package com.yanmaohu.yulia;

import com.yanmaohu.yulia.bean.*;
import com.yanmaohu.yulia.statement.*;
import com.yanmaohu.yulia.token.Token;

import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Bright Lee
 */
public final class Parser {

    private final Scanner scanner;
    private final List<Token> buffer = new ArrayList<>();
    private int bufferBeginIndex;

    public Parser(Reader reader) {
        scanner = new Scanner(reader);
    }

    public Parser(InputStream in, String charseName) {
        scanner = new Scanner(in, charseName);
    }

    public Parser(InputStream in) {
        scanner = new Scanner(in, "UTF-8");
    }

    public Parser(String sql) {
        scanner = new Scanner(sql);
    }

    public Statement parse() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isEof()) {
            return null;
        }
        Statement statement;
        if (token.isAlter()) {
            Token token1 = scan();
            if (token1.isTable()) {
                reset(markedIndex);
                statement = parseAlterTableStatement();
            } else if (token1.isRegularIdentifier() && "DOMAIN".equalsIgnoreCase(token1.getContent())) {
                reset(markedIndex);
                statement = parseAlterDomainStatement();
            } else {
                throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to alter satement.");
            }
        } else if (token.isClose()) {
            reset(markedIndex);
            statement = parseCloseStatement();
        } else if (token.isCommit()) {
            reset(markedIndex);
            statement = parseCommitStatement();
        } else if (token.isConnect()) {
            reset(markedIndex);
            statement = parseConnectStatement();
        } else if (token.isCreate()) {
            Token token1 = scan();
            if (token1.isRegularIdentifier()) {
                if ("ASSERTION".equalsIgnoreCase(token1.getContent())) {
                    reset(markedIndex);
                    statement = parseAssertionDefinition();
                } else if ("COLLATION".equalsIgnoreCase(token1.getContent())) {
                    reset(markedIndex);
                    statement = parseCollationDefinition();
                } else if ("DOMAIN".equalsIgnoreCase(token1.getContent())) {
                    reset(markedIndex);
                    statement = parseDomainDefinition();
                } else if ("SCHEMA".equalsIgnoreCase(token1.getContent())) {
                    reset(markedIndex);
                    statement = parseSchemaDefinition();
                } else if ("VIEW".equalsIgnoreCase(token1.getContent())) {
                    reset(markedIndex);
                    statement = parseViewDefinition();
                } else {
                    throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ASSERTION, COLLATION or DOMAIN.");
                }
            } else if (token1.isCharacter()) {
                reset(markedIndex);
                statement = parseCharacterSetDefinition();
            } else if (token1.isFunction()) {
                reset(markedIndex);
                statement = parseSchemaFunction();
            } else if (token1.isProcedure()) {
                reset(markedIndex);
                statement = parseSchemaProcedure();
            } else if (token1.isTable()) {
                reset(markedIndex);
                statement = parseTableDefinition();
            } else if (token1.isTranslation()) {
                reset(markedIndex);
                statement = parseTransliterationDefinition();
            } else if (token1.isTrigger()) {
                reset(markedIndex);
                statement = parseTriggerDefinition();
            } else {
                throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to CREATE statement.");
            }
        } else if (token.isDeallocate()) {
            reset(markedIndex);
            statement = parseTransliterationDefinition();
        } else if (token.isDeclare()) {
            statement = parseDeclareCursorStatement();
        } else if (token.isDelete()) {
            reset(markedIndex);
            statement = parseDeleteStatement();
        } else if (token.isDescribe()) {
            reset(markedIndex);
            statement = parseDescribeStatement();
        } else if (token.isRegularIdentifier() && "DESCRIPTOR".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            statement = parseSystemDescriptorStatement();
        } else if (token.isExecute()) {
            reset(markedIndex);
            statement = parseExecuteStatement();
        } else if (token.isFetch()) {
            reset(markedIndex);
            statement = parseFetchStatement();
        } else if (token.isGet()) {
            reset(markedIndex);
            statement = parseGetDiagnosticsStatement();
        } else if (token.isGrant()) {
            reset(markedIndex);
            statement = parseGrantStatement();
        } else if (token.isInsert()) {
            reset(markedIndex);
            statement = parseInsertStatement();
        } else if (token.isMerge()) {
            reset(markedIndex);
            statement = parseMergeStatement();
        } else if (token.isOpen()) {
            reset(markedIndex);
            statement = parseOpenStatement();
        } else if (token.isPrepare()) {
            reset(markedIndex);
            statement = parsePrepareStatement();
        } else if (token.isRevoke()) {
            reset(markedIndex);
            statement = parseRevokeStatement();
        } else if (token.isRollback()) {
            reset(markedIndex);
            statement = parseRollbackStatement();
        } else if (token.isSavepoint()) {
            reset(markedIndex);
            statement = parseSavepointStatement();
        } else if (token.isSelect()) {
            reset(markedIndex);
            statement = parseQuerySpecification();
        } else if (token.isSet()) {
            reset(markedIndex);
            statement = parseSetStatement();
        } else if (token.isUpdate()) {
            reset(markedIndex);
            statement = parseUpdateStatement();
        } else {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to any SQL statement.");
        }
        int markedIndex1 = mark();
        Token token1 = scan();
        if (!token1.isSemicolon()) {
            reset(markedIndex1);
        }
        return statement;
    }

    private CloseStatement parseCloseStatement() {
        CloseStatement closeStatement = tryParseCloseStatement();
        if (closeStatement != null) {
            return closeStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to close statement.");
    }

    private CloseStatement tryParseCloseStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isClose()) {
            reset(markedIndex);
            return null;
        }
        LocalQualifiedName cursorName = parseLocalQualifiedName();
        return new CloseStatement(token.getBeginIndex(), cursorName);
    }

    private GrantStatement parseGrantStatement() {
        GrantStatement grantStatement = tryParseGrantStatement();
        if (grantStatement != null) {
            return grantStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to grant statement.");
    }

    private InsertStatement parseInsertStatement() {
        InsertStatement insertStatement = tryParseInsertStatement();
        if (insertStatement != null) {
            return insertStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to insert statement.");
    }

    // INSERT INTO <insertion target> <insert columns and source>
    private InsertStatement tryParseInsertStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isInsert()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isInto()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a INTO.");
        }
        LocalOrSchemaQualifiedName insertionTarget = parseLocalOrSchemaQualifiedName();
        InsertColumnsAndSource insertColumnsAndSource = parseInsertColumnsAndSource();
        return new InsertStatement(token.getBeginIndex(), insertionTarget, insertColumnsAndSource);
    }

    //   <from subquery>
    // | <from constructor>
    // | <from default>
    private InsertColumnsAndSource parseInsertColumnsAndSource() {
        FromSubquery fromSubquery = tryParseFromSubquery();
        if (fromSubquery != null) {
            return fromSubquery;
        }
        FromConstructor fromConstructor = tryParseFromConstructor();
        if (fromConstructor != null) {
            return fromConstructor;
        }
        FromDefault fromDefault = tryParseFromDefault();
        if (fromDefault != null) {
            return fromDefault;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to insert column and source.");
    }

    // [ <left paren> <insert column list> <right paren> ] [ <override clause> ] <query expression>
    private FromSubquery tryParseFromSubquery() {
        IdentifierList insertColumnList = null;
        int markedIndex = mark();
        Token token = scan();
        if (token.isLeftParen()) {
            insertColumnList = parseIdentifierList();
            Token token1 = scan();
            if (!token1.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
            }
        } else {
            reset(markedIndex);
        }
        OverrideClause overrideClause = tryParseOverrideClause();
        QueryExpression queryExpression = tryParseQueryExpression();
        if (queryExpression == null) {
            reset(markedIndex);
            return null;
        }
        return new FromSubquery(token.getBeginIndex(), insertColumnList, overrideClause, queryExpression);
    }

    private List<Identifier> tryParseInsertColumnList() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        Identifier insertColumn = parseIdentifier();
        List<Identifier> insertColumnList = new ArrayList<>();
        insertColumnList.add(insertColumn);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            Identifier insertColumn1 = parseIdentifier();
            insertColumnList.add(insertColumn1);
        }
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return insertColumnList;
    }

    // [ <left paren> <insert column list> <right paren> ] [ <override clause> ] VALUES <contextually typed row value expression list>
    private FromConstructor tryParseFromConstructor() {
        IdentifierList insertColumnList = null;
        int markedIndex = mark();
        Token token = scan();
        if (token.isLeftParen()) {
            insertColumnList = parseIdentifierList();
            Token token1 = scan();
            if (!token1.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
            }
        } else {
            reset(markedIndex);
        }
        OverrideClause overrideClause = tryParseOverrideClause();
        Token token1 = scan();
        if (!token1.isValues()) {
            reset(markedIndex);
            return null;
        }
        ContextuallyTypedRowValueExpressionList contextuallyTypedRowValueExpressionList
            = parseContextuallyTypedRowValueExpressionList();
        return new FromConstructor(token.getBeginIndex(), insertColumnList, overrideClause, contextuallyTypedRowValueExpressionList);
    }

    private ContextuallyTypedRowValueExpressionList parseContextuallyTypedRowValueExpressionList() {
        Token token = scan();
        if (!token.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a (.");
        }
        ContextuallyTypedRowValueExpression contextuallyTypedRowValueExpression
            = parseContextuallyTypedRowValueExpression();
        List<ContextuallyTypedRowValueExpression> contextuallyTypedRowValueExpressionList
            = new ArrayList<>();
        contextuallyTypedRowValueExpressionList.add(contextuallyTypedRowValueExpression);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            ContextuallyTypedRowValueExpression contextuallyTypedRowValueExpression1
                = parseContextuallyTypedRowValueExpression();
            contextuallyTypedRowValueExpressionList.add(contextuallyTypedRowValueExpression1);
        }
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new ContextuallyTypedRowValueExpressionList(token.getBeginIndex(), token1.getEndIndex(), contextuallyTypedRowValueExpressionList);
    }

    // DEFAULT VALUES
    private FromDefault tryParseFromDefault() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDefault()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isValues()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a VALUES.");
        }
        return new FromDefault(token.getBeginIndex(), token1.getEndIndex());
    }

    // <grant privilege statement> | <grant role statement>
    private GrantStatement tryParseGrantStatement() {
        GrantPrivilegeStatement grantPrivilegeStatement = tryParseGrantPrivilegeStatement();
        if (grantPrivilegeStatement != null) {
            return grantPrivilegeStatement;
        }
        GrantRoleStatement grantRoleStatement = tryParseGrantRoleStatement();
        if (grantRoleStatement != null) {
            return grantRoleStatement;
        }
        return null;
    }

    private GrantPrivilegeStatement tryParseGrantPrivilegeStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isGrant()) {
            reset(markedIndex);
            return null;
        }
        Privileges privileges = tryParsePrivileges();
        if (privileges == null) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isTo()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a TO.");
        }
        GranteeList granteeList = parseGranteeList();
        int withHierarchyOptionEndIndex = tryParseWithHierarchyOption();
        int withGrantOptionEndIndex = tryParseWithGrantOption();
        Grantor grantor;
        int markedIndex3 = mark();
        Token token3 = scan();
        if (token3.isRegularIdentifier() && "GRANTED".equalsIgnoreCase(token3.getContent())) {
            Token token4 = scan();
            if (!token4.isBy()) {
                throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a BY.");
            }
            grantor = parseGrantor();
        } else {
            reset(markedIndex3);
            grantor = null;
        }
        int endIndex = granteeList.getEndIndex();
        if (withHierarchyOptionEndIndex > -1) {
            endIndex = withHierarchyOptionEndIndex;
        }
        if (withGrantOptionEndIndex > -1) {
            endIndex = withGrantOptionEndIndex;
        }
        if (grantor != null) {
            endIndex = grantor.getEndIndex();
        }
        return new GrantPrivilegeStatement(
                token.getBeginIndex(),
                endIndex,
                privileges,
                granteeList,
                withHierarchyOptionEndIndex > -1,
                withGrantOptionEndIndex > -1,
                grantor);
    }

    private Grantor parseGrantor() {
        Grantor grantor = tryParseGrantor();
        if (grantor != null) {
            return grantor;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to grantor.");
    }

    private Grantor tryParseGrantor() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isCurrentUser()) {
            return new Grantor(token.getBeginIndex(), token.getEndIndex(), true, false);
        }
        if (token.isCurrentRole()) {
            return new Grantor(token.getBeginIndex(), token.getEndIndex(), false, true);
        }
        reset(markedIndex);
        return null;
    }

    private int tryParseWithHierarchyOption() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isWith()) {
            reset(markedIndex);
            return -1;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"HIERARCHY".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return -1;
        }
        Token token2 = scan();
        if (!token2.isRegularIdentifier() || !"OPTION".equalsIgnoreCase(token2.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a OPTION.");
        }
        return token2.getEndIndex();
    }

    private int tryParseWithGrantOption() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isWith()) {
            reset(markedIndex);
            return -1;
        }
        Token token1 = scan();
        if (!token1.isGrant()) {
            reset(markedIndex);
            return -1;
        }
        Token token2 = scan();
        if (!token2.isRegularIdentifier() || !"OPTION".equalsIgnoreCase(token2.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a OPTION.");
        }
        return token2.getEndIndex();
    }

    private Privileges parsePrivileges() {
        Privileges privileges = tryParsePrivileges();
        if (privileges != null) {
            return privileges;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to privileges.");
    }

    private Privileges tryParsePrivileges() {
        ObjectPrivileges objectPrivileges = tryParseObjectPrivileges();
        if (objectPrivileges == null) {
            return null;
        }
        Token token = scan();
        if (!token.isOn()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a ON.");
        }
        ObjectName objectName = parseObjectName();
        return new Privileges(objectPrivileges, objectName);
    }

    private ObjectPrivileges tryParseObjectPrivileges() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isAll()) {
            Token token1 = scan();
            if (!token1.isRegularIdentifier() || !"PRIVILEGES".equalsIgnoreCase(token1.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing PRIVILEGES.");
            }
            return new AllPrivileges(token.getBeginIndex(), token1.getEndIndex());
        }
        reset(markedIndex);
        ActionList actionList = tryParseActionList();
        return actionList;
    }

    private ActionList parseActionList() {
        ActionList actionList = tryParseActionList();
        if (actionList != null) {
            return actionList;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing action.");
    }

    private ActionList tryParseActionList() {
        Action action = parseAction();
        List<Action> actionList = new ArrayList<>();
        actionList.add(action);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            Action action1 = parseAction();
            actionList.add(action1);
        }
        return new ActionList(actionList);
    }

    private Action parseAction() {
        Action action = tryParseAction();
        if (action != null) {
            return action;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to action.");
    }

    private Action tryParseAction() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isSelect()) {
            Token token1 = scan();
            if (!token1.isLeftParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
            }
            IdentifierList privilegeColumnList;
            List<SpecificRoutineDesignator> privilegeMethodList;
            int markedIndex2 = mark();
            Token token2 = scan();
            if (token2.isSpecific()
                    || (token2.isRegularIdentifier() && "ROUTINE".equalsIgnoreCase(token2.getContent()))
                    || token2.isFunction()
                    || token2.isProcedure()
                    || (token2.isRegularIdentifier() && "INSTANCE".equalsIgnoreCase(token2.getContent()))
                    || token2.isStatic()
                    || (token2.isRegularIdentifier() && "CONSTRUCTOR".equalsIgnoreCase(token2.getContent()))
                    || token2.isMethod()) {
                reset(markedIndex2);
                privilegeColumnList = null;
                privilegeMethodList = parsePrivilegeMethodList();
            } else {
                reset(markedIndex2);
                privilegeColumnList = parseIdentifierList();
                privilegeMethodList = null;
            }
            Token token3 = scan();
            if (!token3.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
            }
            if (privilegeColumnList != null) {
                return new SelectPrivilegeColumnListAction(token.getBeginIndex(), token2.getEndIndex(), privilegeColumnList);
            }
            return new SelectPrivilegeMethodListAction(
                    token.getBeginIndex(),
                    token2.getEndIndex(),
                    privilegeMethodList
            );
        }
        if (token.isDelete()) {
            return new DeleteAction(token.getBeginIndex(), token.getEndIndex());
        }
        if (token.isInsert()) {
            IdentifierList privilegeColumnList;
            int endIndex = token.getEndIndex();
            int markedIndex1 = mark();
            Token token1 = scan();
            if (token1.isLeftParen()) {
                privilegeColumnList = parseIdentifierList();
                Token token2 = scan();
                if (!token2.isRightParen()) {
                    throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
                }
                endIndex = token2.getEndIndex();
            } else {
                reset(markedIndex1);
                privilegeColumnList = null;
            }
            return new InsertPrivilegeColumnListAction(
                    token.getBeginIndex(),
                    endIndex,
                    privilegeColumnList);
        }
        if (token.isUpdate()) {
            IdentifierChain privilegeColumnList;
            int endIndex = token.getEndIndex();
            int markedIndex1 = mark();
            Token token1 = scan();
            if (token1.isLeftParen()) {
                privilegeColumnList = parseIdentifierChain();
                Token token2 = scan();
                if (!token2.isRightParen()) {
                    throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
                }
                endIndex = token2.getEndIndex();
            } else {
                reset(markedIndex1);
                privilegeColumnList = null;
            }
            return new UpdatePrivilegeColumnListAction(
                    token.getBeginIndex(),
                    endIndex,
                    privilegeColumnList);
        }
        if (token.isReferences()) {
            IdentifierChain privilegeColumnList;
            int endIndex = token.getEndIndex();
            int markedIndex1 = mark();
            Token token1 = scan();
            if (token1.isLeftParen()) {
                privilegeColumnList = parseIdentifierChain();
                Token token2 = scan();
                if (!token2.isRightParen()) {
                    throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
                }
                endIndex = token2.getEndIndex();
            } else {
                reset(markedIndex1);
                privilegeColumnList = null;
            }
            return new ReferencesPrivilegeColumnListAction(
                    token.getBeginIndex(),
                    endIndex,
                    privilegeColumnList);
        }
        if (token.isRegularIdentifier() && "USAGE".equalsIgnoreCase(token.getContent())) {
            return new UsageAction(token.getBeginIndex(), token.getEndIndex());
        }
        if (token.isTrigger()) {
            return new TriggerAction(token.getBeginIndex(), token.getEndIndex());
        }
        if (token.isRegularIdentifier() && "UNDER".equalsIgnoreCase(token.getContent())) {
            return new UnderAction(token.getBeginIndex(), token.getEndIndex());
        }
        if (token.isExecute()) {
            return new ExecuteAction(token.getBeginIndex(), token.getEndIndex());
        }
        reset(markedIndex);
        return null;
    }

    private ObjectName parseObjectName() {
        ObjectName objectName = tryParseObjectName();
        if (objectName != null) {
            return objectName;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to object name.");
    }

    private ObjectName tryParseObjectName() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isTable()) {
            LocalOrSchemaQualifiedName tableName = parseLocalOrSchemaQualifiedName();
            return new TableObjectName(token.getBeginIndex(), true, tableName);
        }
        if (token.isRegularIdentifier() && "DOMAIN".equalsIgnoreCase(token.getContent())) {
            SchemaQualifiedName domainName = parseSchemaQualifiedName();
            return new DomainObjectName(token.getBeginIndex(), domainName);
        }
        if (token.isRegularIdentifier() && "COLLATION".equalsIgnoreCase(token.getContent())) {
            SchemaQualifiedName collationName = parseSchemaQualifiedName();
            return new CollationObjectName(token.getBeginIndex(), collationName);
        }
        if (token.isCharacter()) {
            Token token1 = scan();
            if (!token1.isSet()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SET.");
            }
            CharacterSetName characterSetName = parseCharacterSetName();
            return new CharacterSetObjectName(token.getBeginIndex(), characterSetName);
        }
        if (token.isTranslation()) {
            SchemaQualifiedName translationName = parseSchemaQualifiedName();
            return new TranslationObjectName(token.getBeginIndex(), translationName);
        }
        if (token.isRegularIdentifier() && "TYPE".equalsIgnoreCase(token.getContent())) {
            SchemaQualifiedName schemaResolvedUserDefinedTypeName = parseSchemaQualifiedName();
            return new TypeObjectName(token.getBeginIndex(), schemaResolvedUserDefinedTypeName);
        }
        if (token.isRegularIdentifier() && "SEQUENCE".equalsIgnoreCase(token.getContent())) {
            SchemaQualifiedName schemaResolvedUserDefinedTypeName = parseSchemaQualifiedName();
            return new SequenceObjectName(token.getBeginIndex(), schemaResolvedUserDefinedTypeName);
        }
        reset(markedIndex);
        SpecificRoutineDesignator specificRoutineDesignator = tryParseSpecificRoutineDesignator();
        if (specificRoutineDesignator != null) {
            return specificRoutineDesignator;
        }
        LocalOrSchemaQualifiedName tableName = tryParseLocalOrSchemaQualifiedName();
        if (tableName != null) {
            return new TableObjectName(token.getBeginIndex(), false, tableName);
        }
        return null;
    }

    private SpecificRoutineDesignator parseSpecificRoutineDesignator() {
        SpecificRoutineDesignator specificRoutineDesignator = tryParseSpecificRoutineDesignator();
        if (specificRoutineDesignator != null) {
            return specificRoutineDesignator;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to specific routine designator.");
    }

    private SpecificRoutineDesignator tryParseSpecificRoutineDesignator() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isSpecific()) {
            RoutineType routineType = parseRoutineType();
            SchemaQualifiedName specificName = parseSchemaQualifiedName();
            return new SpecificRoutineDesignatorSubtype1(token.getBeginIndex(), routineType, specificName);
        }
        reset(markedIndex);
        RoutineType routineType = tryParseRoutineType();
        if (routineType == null) {
            return null;
        }
        MemberNameAlternatives memberNameAlternatives = parseMemberNameAlternatives();
        int endIndex = memberNameAlternatives.getEndIndex();
        DataTypeList dataTypeList = tryParseDataTypeList();
        if (dataTypeList != null) {
            endIndex = dataTypeList.getEndIndex();
        }
        SchemaQualifiedName schemaResolvedUserDefinedTypeName;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isFor()) {
            schemaResolvedUserDefinedTypeName = parseSchemaQualifiedName();
            endIndex = schemaResolvedUserDefinedTypeName.getEndIndex();
        } else {
            reset(markedIndex1);
            schemaResolvedUserDefinedTypeName = null;
        }
        return new SpecificRoutineDesignatorSubtype2(
                token.getBeginIndex(),
                endIndex,
                routineType,
                memberNameAlternatives,
                dataTypeList,
                schemaResolvedUserDefinedTypeName);
    }

    private RoutineType parseRoutineType() {
        RoutineType routineType = tryParseRoutineType();
        if (routineType != null) {
            return routineType;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to routine type.");
    }

    private RoutineType tryParseRoutineType() {
        boolean routine = false;
        boolean function = false;
        boolean procedure = false;
        boolean instanceMethod = false;
        boolean staticMethod = false;
        boolean constructorMethod = false;
        boolean method = false;
        int markedIndex = mark();
        Token token = scan();
        int beginIndex = token.getBeginIndex();
        int endIndex = token.getEndIndex();
        if (token.isRegularIdentifier() && "ROUTINE".equalsIgnoreCase(token.getContent())) {
            routine = true;
            return new RoutineType(
                    beginIndex,
                    endIndex,
                    routine,
                    function,
                    procedure,
                    instanceMethod,
                    staticMethod,
                    constructorMethod,
                    method
            );
        }
        if (token.isFunction()) {
            function = true;
            return new RoutineType(
                    beginIndex,
                    endIndex,
                    routine,
                    function,
                    procedure,
                    instanceMethod,
                    staticMethod,
                    constructorMethod,
                    method
            );
        }
        if (token.isProcedure()) {
            procedure = true;
            return new RoutineType(
                    beginIndex,
                    endIndex,
                    routine,
                    function,
                    procedure,
                    instanceMethod,
                    staticMethod,
                    constructorMethod,
                    method
            );
        }
        if (token.isRegularIdentifier() && "INSTANCE".equalsIgnoreCase(token.getContent())) {
            Token token1 = scan();
            if (!"METHOD".equalsIgnoreCase(token1.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a METHOD.");
            }
            endIndex = token1.getEndIndex();
            instanceMethod = true;
            return new RoutineType(
                    beginIndex,
                    endIndex,
                    routine,
                    function,
                    procedure,
                    instanceMethod,
                    staticMethod,
                    constructorMethod,
                    method
            );
        }
        if (token.isStatic()) {
            Token token1 = scan();
            if (!"METHOD".equalsIgnoreCase(token1.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a METHOD.");
            }
            endIndex = token1.getEndIndex();
            staticMethod = true;
            return new RoutineType(
                    beginIndex,
                    endIndex,
                    routine,
                    function,
                    procedure,
                    instanceMethod,
                    staticMethod,
                    constructorMethod,
                    method
            );
        }
        if (token.isRegularIdentifier() && "CONSTRUCTOR".equalsIgnoreCase(token.getContent())) {
            Token token1 = scan();
            if (!"METHOD".equalsIgnoreCase(token1.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a METHOD.");
            }
            endIndex = token1.getEndIndex();
            constructorMethod = true;
            return new RoutineType(
                    beginIndex,
                    endIndex,
                    routine,
                    function,
                    procedure,
                    instanceMethod,
                    staticMethod,
                    constructorMethod,
                    method
            );
        }
        if (token.isMethod()) {
            method = true;
            return new RoutineType(
                    beginIndex,
                    endIndex,
                    routine,
                    function,
                    procedure,
                    instanceMethod,
                    staticMethod,
                    constructorMethod,
                    method
            );
        }
        reset(markedIndex);
        return null;
    }

    private MemberNameAlternatives parseMemberNameAlternatives() {
        MemberNameAlternatives memberNameAlternatives = tryParseMemberNameAlternatives();
        if (memberNameAlternatives != null) {
            return memberNameAlternatives;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to member name alternatives.");
    }

    private MemberNameAlternatives tryParseMemberNameAlternatives() {
        SchemaQualifiedName schemaQualifiedName = tryParseSchemaQualifiedName();
        if (schemaQualifiedName == null) {
            return null;
        }
        SchemaName schemaName = schemaQualifiedName.getSchemaName();
        if (schemaName == null) {
            return schemaQualifiedName.getQualifiedIdentifier();
        }
        return schemaQualifiedName;
    }

    private GranteeList parseGranteeList() {
        Grantee grantee = parseGrantee();
        List<Grantee> granteeList = new ArrayList<>();
        granteeList.add(grantee);
        while (true) {
            int markedIndex2 = mark();
            Token token2 = scan();
            if (!token2.isComma()) {
                reset(markedIndex2);
                break;
            }
            Grantee grantee1 = parseGrantee();
            granteeList.add(grantee1);
        }
        return new GranteeList(granteeList);
    }

    private Grantee parseGrantee() {
        Grantee grantee = tryParseGrantee();
        if (grantee != null) {
            return grantee;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to grantee.");
    }

    private Grantee tryParseGrantee() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "PUBLIC".equalsIgnoreCase(token.getContent())) {
            return new Grantee(token.getBeginIndex(), token.getEndIndex(), true, null);
        }
        if (!token.isRegularIdentifier() && !token.isUnicodeDelimiterBody()) {
            reset(markedIndex);
            return null;
        }
        Identifier authorizationIdentifier = new Identifier(token);
        return new Grantee(token.getBeginIndex(), token.getEndIndex(), false, authorizationIdentifier);
    }

    private GrantRoleStatement tryParseGrantRoleStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isGrant()) {
            reset(markedIndex);
            return null;
        }
        Identifier roleGranted = tryParseIdentifier();
        if (roleGranted == null) {
            reset(markedIndex);
            return null;
        }
        IdentifierList roleGrantedList = parseIdentifierList();
        Token token3 = scan();
        if (!token3.isTo()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a TO.");
        }
        GranteeList granteeList = parseGranteeList();
        int withAdminOptionEndIndex;
        int markedIndex4 = mark();
        Token token4 = scan();
        if (token4.isWith()) {
            Token token5 = scan();
            if (!token5.isRegularIdentifier() || !"ADMIN".equalsIgnoreCase(token5.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing a ADMIN.");
            }
            Token token6 = scan();
            if (!token6.isRegularIdentifier() || !"OPTION".equalsIgnoreCase(token6.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token6.getBeginIndex(), "Missing a OPTION.");
            }
            withAdminOptionEndIndex = token6.getEndIndex();
        } else {
            withAdminOptionEndIndex = -1;
            reset(markedIndex4);
        }
        Grantor grantor;
        int markedIndex7 = mark();
        Token token7 = scan();
        if (token7.isRegularIdentifier() && "GRANTED".equalsIgnoreCase(token7.getContent())) {
            Token token8 = scan();
            if (!token8.isBy()) {
                throw new YuliaException(getSql(), getLineNumber(), token8.getBeginIndex(), "Missing a BY.");
            }
            grantor = parseGrantor();
        } else {
            reset(markedIndex7);
            grantor = null;
        }
        int endIndex = granteeList.getEndIndex();
        if (withAdminOptionEndIndex > -1) {
            endIndex = withAdminOptionEndIndex;
        }
        if (grantor != null) {
            endIndex = grantor.getEndIndex();
        }
        return new GrantRoleStatement(
                token.getBeginIndex(),
                endIndex,
                roleGrantedList,
                granteeList,
                withAdminOptionEndIndex > -1,
                grantor);
    }

    private SavepointStatement parseSavepointStatement() {
        SavepointStatement savepointStatement = tryParseSavepointStatement();
        if (savepointStatement != null) {
            return savepointStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to savepoint statement.");
    }

    // SELECT [ <set quantifier> ] <select list> <table expression>
    private QuerySpecification parseQuerySpecification() {
        QuerySpecification querySpecification = tryParseQuerySpecification();
        if (querySpecification != null) {
            return querySpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to query specification.");
    }

    // <asterisk> | <select sublist> [ { <comma> <select sublist> }... ]
    private SelectList parseSelectList() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isAsterisk()) {
            return new SelectList(token.getBeginIndex(), token.getEndIndex());
        }
        reset(markedIndex);
        SelectSublist selectSublist = parseSelectSublist();
        List<SelectSublist> selectSublistList = new ArrayList<>();
        selectSublistList.add(selectSublist);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            SelectSublist selectSublist1 = parseSelectSublist();
            selectSublistList.add(selectSublist1);
        }
        return new SelectList(selectSublistList);
    }

    // <derived column> | <qualified asterisk>
    private SelectSublist parseSelectSublist() {
        DerivedColumn derivedColumn = tryParseDerivedColumn();
        if (derivedColumn != null) {
            return derivedColumn;
        }
        QualifiedAsterisk qualifiedAsterisk = tryParseQualifiedAsterisk();
        if (qualifiedAsterisk != null) {
            return qualifiedAsterisk;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to select sublist.");
    }

    private DerivedColumn tryParseDerivedColumn() {
        ValueExpression valueExpression = tryParseValueExpression();
        if (valueExpression == null) {
            return null;
        }
        AsClause asClause = tryParseAsClause();
        return new DerivedColumn(valueExpression, asClause);
    }

    private AsClause tryParseAsClause() {
        int beginIndex;
        Identifier columnName;
        int markedIndex = mark();
        Token token = scan();
        if (token.isAs()) {
            beginIndex = token.getBeginIndex();
            columnName = parseIdentifier();
        } else {
            reset(markedIndex);
            columnName = tryParseIdentifier();
            if (columnName == null) {
                reset(markedIndex);
                return null;
            }
            beginIndex = columnName.getBeginIndex();
        }
        return new AsClause(beginIndex, columnName);
    }

    // <asterisked identifier chain> <period> <asterisk> | <all fields reference>
    private QualifiedAsterisk tryParseQualifiedAsterisk() {
        QualifiedAsterisk qualifiedAsterisk = tryParseAsteriskedIdentifierChainPeriodAsterisk();
        if (qualifiedAsterisk != null) {
            return qualifiedAsterisk;
        }
        qualifiedAsterisk = tryParseAllFieldsReference();
        if (qualifiedAsterisk != null) {
            return qualifiedAsterisk;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to qualified asterisk.");
    }

    // <asterisked identifier chain> <period> <asterisk>
    private QualifiedAsterisk tryParseAsteriskedIdentifierChainPeriodAsterisk() {
        int markedIndex = mark();
        IdentifierChain asteriskedIdentifierChain = tryParseIdentifierChain();
        if (asteriskedIdentifierChain == null) {
            return null;
        }
        Token token1 = scan();
        if (!token1.isPeriod()) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isAsterisk()) {
            reset(markedIndex);
            return null;
        }
        return new QualifiedAsterisk(token2.getEndIndex(), asteriskedIdentifierChain);
    }

    // <value expression primary> <period> <asterisk> [ AS <left paren> <all fields column name list> <right paren> ]
    private QualifiedAsterisk tryParseAllFieldsReference() {
        ValueExpressionPrimary valueExpressionPrimary = tryParseValueExpressionPrimary();
        if (valueExpressionPrimary == null) {
            return null;
        }
        Token token = scan();
        if (!token.isPeriod()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a ..");
        }
        Token token1 = scan();
        if (!token1.isAsterisk()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a *.");
        }
        int endIndex = token1.getEndIndex();
        int markedIndex2 = mark();
        Token token2 = scan();
        if (token2.isAs()) {
            Token token3 = scan();
            if (!token3.isLeftParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a (.");
            }
            IdentifierList allFieldsColumnNameList = parseIdentifierList();
            Token token4 = scan();
            if (!token4.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a ).");
            }
            endIndex = token4.getEndIndex();
            return new QualifiedAsterisk(endIndex, valueExpressionPrimary, allFieldsColumnNameList);
        }
        reset(markedIndex2);
        return new QualifiedAsterisk(endIndex, valueExpressionPrimary);
    }

    private ValueExpressionPrimary parseValueExpressionPrimary() {
        ValueExpressionPrimary valueExpressionPrimary = tryParseValueExpressionPrimary();
        if (valueExpressionPrimary != null) {
            return valueExpressionPrimary;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to value expression primary.");
    }

    private ValueExpressionPrimary tryParseValueExpressionPrimary() {
        ParenthesizedValueExpression parenthesizedValueExpression
                = tryParseParenthesizedValueExpression();
        if (parenthesizedValueExpression != null) {
            return parenthesizedValueExpression;
        }
        NonparenthesizedValueExpressionPrimary nonparenthesizedValueExpressionPrimary
                = tryParseNonparenthesizedValueExpressionPrimary();
        if (nonparenthesizedValueExpressionPrimary != null) {
            return nonparenthesizedValueExpressionPrimary;
        }
        return null;
    }

    private ParenthesizedValueExpression tryParseParenthesizedValueExpression() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        ValueExpression valueExpression = parseValueExpression();
        Token token1 = scan();
        if (token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new ParenthesizedValueExpression(token.getBeginIndex(), token1.getEndIndex(), valueExpression);
    }

    //   <set catalog statement>
    // | <set connection statement>
    // | <set constraints mode statement>
    // | <set names statement>
    // | <set schema statement>
    // | <set session user identifier statement>
    // | <set local time zone statement>
    // | <set transaction statement>
    private Statement parseSetStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a SET.");
        }
        Token token1 = scan();
        if (token1.isRegularIdentifier() && "CATALOG".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return parseSetCatalogStatement();
        }
        if (token1.isRegularIdentifier() && "CONNECTION".equalsIgnoreCase(token1.getContent())) {
            // SET CONNECTION <connection object>
            reset(markedIndex);
            return parseSetConnectionStatement();
        }
        if (token1.isRegularIdentifier() && "CONSTRAINTS".equalsIgnoreCase(token1.getContent())) {
            // SET CONSTRAINTS <constraint name list> { DEFERRED | IMMEDIATE }
            reset(markedIndex);
            return parseSetConstraintsModeStatement();
        }
        if (token1.isRegularIdentifier() && "NAMES".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return parseSetNamesStatement();
        }
        if (token1.isRegularIdentifier() && "SCHEMA".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return parseSetSchemaStatement();
        }
        if (token1.isRegularIdentifier() && "SESSION".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return parseSetSessionUserIdentifierStatement();
        }
        if (token1.isTime()) {
            reset(markedIndex);
            return parseSetLocalTimeZoneStatement();
        }
        reset(markedIndex);
        return parseSetTransactionStatement();
    }

    private SetConnectionStatement parseSetConnectionStatement() {
        SetConnectionStatement setConnectionStatement = tryParseSetConnectionStatement();
        if (setConnectionStatement != null) {
            return setConnectionStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to set connection statement.");
    }

    // SET CONNECTION <connection object>
    private SetConnectionStatement tryParseSetConnectionStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"CONNECTION".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        ConnectionObject connectionObject = parseConnectionObject();
        return new SetConnectionStatement(token.getBeginIndex(), connectionObject);
    }

    private TransactionMode parseTransactionMode() {
        TransactionMode transactionMode = tryParseTransactionMode();
        if (transactionMode != null) {
            return transactionMode;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to transaction mode.");
    }

    // <isolation level> | <transaction access mode> | <diagnostics size>
    private TransactionMode tryParseTransactionMode() {
        IsolationLevel isolationLevel = tryParseIsolationLevel();
        if (isolationLevel != null) {
            return isolationLevel;
        }
        TransactionAccessMode transactionAccessMode = tryParseTransactionAccessMode();
        if (transactionAccessMode != null) {
            return transactionAccessMode;
        }
        DiagnosticsSize diagnosticsSize = tryParseDiagnosticsSize();
        if (diagnosticsSize != null) {
            return diagnosticsSize;
        }
        return null;
    }

    // ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
    private IsolationLevel tryParseIsolationLevel() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isIsolation()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"LEVEL".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a LEVEL.");
        }
        Token token2 = scan();
        if (token2.isRegularIdentifier() && "READ".equalsIgnoreCase(token2.getContent())) {
            Token token3 = scan();
            if (token3.isRegularIdentifier() && "UNCOMMITTED".equalsIgnoreCase(token3.getContent())) {
                return new IsolationLevel(token.getBeginIndex(), token3.getEndIndex(), true, false, false, false);
            }
            if (token3.isRegularIdentifier() && "COMMITTED".equalsIgnoreCase(token3.getContent())) {
                return new IsolationLevel(token.getBeginIndex(), token3.getEndIndex(), false, true, false, false);
            }
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a UNCOMMITTED or COMMITTED.");
        }
        if (token2.isRegularIdentifier() && "REPEATABLE".equalsIgnoreCase(token2.getContent())) {
            Token token3 = scan();
            if (token3.isRegularIdentifier() && "READ".equalsIgnoreCase(token3.getContent())) {
                return new IsolationLevel(token.getBeginIndex(), token3.getEndIndex(), false, false, true, false);
            }
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a READ.");
        }
        if (token2.isRegularIdentifier() && "SERIALIZABLE".equalsIgnoreCase(token2.getContent())) {
            return new IsolationLevel(token.getBeginIndex(), token2.getEndIndex(), false, false, false, true);
        }
        throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a READ, REPEATABLE or SERIALIZABLE.");
    }

    // READ ONLY | READ WRITE
    private TransactionAccessMode tryParseTransactionAccessMode() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"READ".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (token1.isOnly()) {
            return new TransactionAccessMode(token.getBeginIndex(), token1.getEndIndex(), true, false);
        }
        if (token1.isRegularIdentifier() && "WRITE".equalsIgnoreCase(token1.getContent())) {
            return new TransactionAccessMode(token.getBeginIndex(), token1.getEndIndex(), false, true);
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a ONLY or WRITE.");
    }

    // DIAGNOSTICS SIZE <number of conditions>
    private DiagnosticsSize tryParseDiagnosticsSize() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"DIAGNOSTICS".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"SIZE".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SIZE.");
        }
        SimpleValueSpecification numberOfConditions = parseSimpleValueSpecification();
        return new DiagnosticsSize(token.getBeginIndex(), numberOfConditions);
    }

    private SetTimeZoneValue parseSetTimeZoneValue() {
        IntervalValueExpression intervalValueExpression = tryParseIntervalValueExpression();
        boolean local = false;
        int beginIndex;
        int endIndex;
        if (intervalValueExpression == null) {
            Token token = scan();
            if (!token.isLocal()) {
                throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to set time zone value.");
            }
            local = true;
            beginIndex = token.getBeginIndex();
            endIndex = token.getEndIndex();
        } else {
            beginIndex = intervalValueExpression.getBeginIndex();
            endIndex = intervalValueExpression.getEndIndex();
        }
        return new SetTimeZoneValue(beginIndex, endIndex, intervalValueExpression, local);
    }

    private IntervalValueExpression parseIntervalValueExpression() {
        IntervalValueExpression intervalValueExpression = tryParseIntervalValueExpression();
        if (intervalValueExpression != null) {
            return intervalValueExpression;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to interval value expression.");
    }

    private IntervalValueExpression tryParseIntervalValueExpression() {
        IntervalValueExpression intervalValueExpression = null;
        int markedIndex = mark();
        Token token = scan();
        if (token.isLeftParen()) {
            DatetimeValueExpression datetimeValueExpression = parseDatetimeValueExpression();
            Token token1 = scan();
            if (!token1.isMinusSign()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a -.");
            }
            DatetimeTerm datetimeTerm = parseDatetimeTerm();
            Token token2 = scan();
            if (!token2.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
            }
            IntervalQualifier intervalQualifier = parseIntervalQualifier();
            intervalValueExpression = new IntervalValueExpressionSubtype(
                    token.getBeginIndex(),
                    datetimeValueExpression,
                    datetimeTerm,
                    intervalQualifier
            );
        } else {
            reset(markedIndex);
            intervalValueExpression = tryParseIntervalTerm();
        }
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (token1.isPlusSign()) {
                IntervalTerm intervalTerm = parseIntervalTerm();
                intervalValueExpression = new IntervalValueExpressionPlusIntervalTerm(
                        intervalValueExpression,
                        intervalTerm
                );
            } else if (token1.isMinusSign()) {
                IntervalTerm intervalTerm = parseIntervalTerm();
                intervalValueExpression = new IntervalValueExpressionMinusIntervalTerm(
                        intervalValueExpression,
                        intervalTerm
                );
            } else {
                reset(markedIndex1);
                break;
            }
        }
        return intervalValueExpression;
    }

    private DatetimeValueExpression parseDatetimeValueExpression() {
        DatetimeValueExpression datetimeValueExpression = tryParseDatetimeValueExpression();
        if (datetimeValueExpression != null) {
            return datetimeValueExpression;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to datetime value expression.");
    }

    //   <datetime term>
    // | <interval value expression> <plus sign> <datetime term>
    // | <datetime value expression> <plus sign> <interval term>
    // | <datetime value expression> <minus sign> <interval term>
    private DatetimeValueExpression tryParseDatetimeValueExpression() {
        DatetimeValueExpression datetimeValueExpression = tryParseDatetimeTerm();
        if (datetimeValueExpression == null) {
            IntervalValueExpression intervalValueExpression = tryParseIntervalValueExpression();
            if (intervalValueExpression == null) {
                return  null;
            }
            Token token = scan();
            if (!token.isPlusSign()) {
                throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a +.");
            }
            DatetimeTerm datetimeTerm = parseDatetimeTerm();
            datetimeValueExpression = new IntervalValueExpressionPlusDatetimeTerm(
                    intervalValueExpression,
                    datetimeTerm
            );
        }
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (token.isPlusSign()) {
                IntervalTerm intervalTerm = parseIntervalTerm();
                datetimeValueExpression = new DatetimeValueExpressionPlusIntervalTerm(
                        datetimeValueExpression,
                        intervalTerm
                );
            } else if (token.isMinusSign()) {
                IntervalTerm intervalTerm = parseIntervalTerm();
                datetimeValueExpression = new DatetimeValueExpressionMinusIntervalTerm(
                        datetimeValueExpression,
                        intervalTerm
                );
            } else {
                reset(markedIndex);
                break;
            }
        }
        return datetimeValueExpression;
    }

    private DatetimeTerm parseDatetimeTerm() {
        DatetimeTerm datetimeTerm = tryParseDatetimeTerm();
        if (datetimeTerm != null) {
            return datetimeTerm;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to datetime term.");
    }

    // <datetime primary> [ <time zone> ]
    private DatetimeTerm tryParseDatetimeTerm() {
        DatetimePrimary datetimePrimary = tryParseDatetimePrimary();
        if (datetimePrimary == null) {
            return null;
        }
        TimeZone timeZone = tryParseTimeZone();
        return new DatetimeTerm(datetimePrimary, timeZone);
    }

    private DatetimePrimary parseDatetimePrimary() {
        DatetimePrimary datetimePrimary = tryParseDatetimePrimary();
        if (datetimePrimary != null) {
            return datetimePrimary;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to datetime primary.");
    }

    // AT <time zone specifier>
    private TimeZone tryParseTimeZone() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAt()) {
            reset(markedIndex);
            return null;
        }
        TimeZoneSpecifier timeZoneSpecifier = parseTimeZoneSpecifier();
        return new TimeZone(token.getBeginIndex(), timeZoneSpecifier);
    }

    // LOCAL | TIME ZONE <interval primary>
    private TimeZoneSpecifier parseTimeZoneSpecifier() {
        Token token = scan();
        if (token.isLocal()) {
            return new TimeZoneSpecifier(token.getBeginIndex(), token.getEndIndex());
        }
        if (token.isTime()) {
            Token token1 = scan();
            if (!token1.isRegularIdentifier() || !"ZONE".equalsIgnoreCase(token1.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ZONE.");
            }
            IntervalPrimary intervalPrimary = parseIntervalPrimary();
            return new TimeZoneSpecifier(token.getBeginIndex(), intervalPrimary);
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to time zone specifier.");
    }

    private DatetimePrimary tryParseDatetimePrimary() {
        ValueExpressionPrimary valueExpressionPrimary = tryParseValueExpressionPrimary();
        if (valueExpressionPrimary != null) {
            return valueExpressionPrimary;
        }
        DatetimeValueFunction datetimeValueFunction = tryParseDatetimeValueFunction();
        if (datetimeValueFunction != null) {
            return datetimeValueFunction;
        }
        return null;
    }

    private IntervalTerm parseIntervalTerm() {
        IntervalTerm intervalTerm = tryParseIntervalTerm();
        if (intervalTerm != null) {
            return intervalTerm;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to interval term.");
    }

    //   <interval factor>
    // | <interval term 2> <asterisk> <factor>
    // | <interval term 2> <solidus> <factor>
    // | <term> <asterisk> <interval factor>
    private IntervalTerm tryParseIntervalTerm() {
        IntervalTerm intervalTerm = tryParseIntervalFactor();
        if (intervalTerm == null) {
            Term term = tryParseTerm();
            if (term == null) {
                return null;
            }
            Token token = scan();
            if (!token.isAsterisk()) {
                throw new YuliaException(getSql(), getLineNumber(), term.getBeginIndex(), "Missing a *.");
            }
            IntervalFactor intervalFactor = parseIntervalFactor();
            intervalTerm = new TermAsteriskIntervalFactor(
                    term,
                    intervalFactor
            );
        }
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (token.isAsterisk()) {
                Factor factor = parseFactor();
                intervalTerm = new IntervalTermAsteriskFactor(
                        intervalTerm,
                        factor
                );
            } else if (token.isSolidus()) {
                Factor factor = parseFactor();
                intervalTerm = new IntervalTermSolidusFactor(
                        intervalTerm,
                        factor
                );
            } else {
                reset(markedIndex);
                break;
            }
        }
        return intervalTerm;
    }

    // [ <sign> ] <interval primary>
    private IntervalFactor parseIntervalFactor() {
        IntervalFactor intervalFactor = tryParseIntervalFactor();
        if (intervalFactor != null) {
            return intervalFactor;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to interval factor.");
    }

    // [ <sign> ] <interval primary>
    private IntervalFactor tryParseIntervalFactor() {
        Sign sign = tryParseSign();
        IntervalPrimary intervalPrimary;
        if (sign == null) {
            intervalPrimary = tryParseIntervalPrimary();
            if (intervalPrimary == null) {
                return null;
            }
        } else {
            intervalPrimary = parseIntervalPrimary();
        }
        return new IntervalFactor(sign, intervalPrimary);
    }

    private IntervalPrimary parseIntervalPrimary() {
        IntervalPrimary intervalPrimary = tryParseIntervalPrimary();
        if (intervalPrimary != null) {
            return intervalPrimary;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to interval primary.");
    }

    //   <value expression primary> [ <interval qualifier> ]
    // | <interval value function>
    private IntervalPrimary tryParseIntervalPrimary() {
        ValueExpressionPrimary valueExpressionPrimary = tryParseValueExpressionPrimary();
        if (valueExpressionPrimary != null) {
            IntervalQualifier intervalQualifier = tryParseIntervalQualifier();
            return new ValueExpressionPrimaryIntervalQualifier(
                    valueExpressionPrimary,
                    intervalQualifier
            );
        }
        IntervalAbsoluteValueFunction intervalValueFunction = tryParseIntervalAbsoluteValueFunction();
        if (intervalValueFunction != null) {
            return intervalValueFunction;
        }
        return null;
    }

    private IntervalQualifier parseIntervalQualifier() {
        IntervalQualifier intervalQualifier = tryParseIntervalQualifier();
        if (intervalQualifier != null) {
            return intervalQualifier;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to interval qualifier.");
    }

    //   <start field> TO <end field>
    // | <single datetime field>
    private IntervalQualifier tryParseIntervalQualifier() {
        NonSecondPrimaryDatetimeField nonSecondPrimaryDatetimeField
                = tryParseNonSecondPrimaryDatetimeField();
        if (nonSecondPrimaryDatetimeField != null) {
            UnsignedInteger intervalLeadingFieldPrecision = null;
            int endIndex = nonSecondPrimaryDatetimeField.getEndIndex();
            int markedIndex = mark();
            Token token = scan();
            if (token.isLeftParen()) {
                intervalLeadingFieldPrecision = parseUnsignedInteger();
                Token token1 = scan();
                if (!token.isRightParen()) {
                    throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
                }
                endIndex = token.getEndIndex();
            } else {
                reset(markedIndex);
            }
            int markedIndex1 = mark();
            Token token1 = scan();
            if (token1.isTo()) {
                NonSecondPrimaryDatetimeField nonSecondPrimaryDatetimeField1
                        = tryParseNonSecondPrimaryDatetimeField();
                if (nonSecondPrimaryDatetimeField1 != null) {
                    StartField startField = new StartField(
                            endIndex,
                            nonSecondPrimaryDatetimeField,
                            intervalLeadingFieldPrecision
                    );
                    EndField endField = new EndField(nonSecondPrimaryDatetimeField1);
                    return new StartFieldToEndField(startField, endField);
                }
                Token token2 = scan();
                if (!token2.isSecond()) {
                    throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a SECOND.");
                }
                int beginIndex1 = token2.getBeginIndex();
                int endIndex1 = token2.getEndIndex();
                UnsignedInteger intervalFractionalSecondsPrecision = null;
                int markedIndex3 = mark();
                Token token3 = scan();
                if (token3.isLeftParen()) {
                    intervalFractionalSecondsPrecision = parseUnsignedInteger();
                    Token token4 = scan();
                    if (!token4.isRightParen()) {
                        throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a ).");
                    }
                    endIndex1 = token4.getEndIndex();
                } else {
                    reset(markedIndex3);
                }
                StartField startField = new StartField(
                        endIndex,
                        nonSecondPrimaryDatetimeField,
                        intervalLeadingFieldPrecision
                );
                EndField endField = new EndField(
                        beginIndex1,
                        endIndex1,
                        intervalFractionalSecondsPrecision
                );
                return new StartFieldToEndField(startField, endField);
            } else {
                reset(markedIndex1);
                return new SingleDatetimeField(
                        endIndex,
                        nonSecondPrimaryDatetimeField,
                        intervalLeadingFieldPrecision
                );
            }
        }
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSecond()) {
            reset(markedIndex);
            return null;
        }
        int endIndex = token.getEndIndex();
        UnsignedInteger intervalLeadingFieldPrecision = null;
        UnsignedInteger intervalFractionalSecondsPrecision = null;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isLeftParen()) {
            intervalLeadingFieldPrecision = parseUnsignedInteger();
            int markedIndex2 = mark();
            Token token2 = scan();
            if (token2.isComma()) {
                intervalFractionalSecondsPrecision = parseUnsignedInteger();
            } else {
                reset(markedIndex2);
            }
            Token token3 = scan();
            if (!token3.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
            }
            endIndex = token3.getEndIndex();
        } else {
            reset(markedIndex1);
        }
        return new SingleDatetimeField(
                token.getBeginIndex(),
                endIndex,
                intervalLeadingFieldPrecision,
                intervalFractionalSecondsPrecision
        );
    }

    // ABS <left paren> <interval value expression> <right paren>
    private IntervalAbsoluteValueFunction tryParseIntervalAbsoluteValueFunction() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"ABS".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        IntervalValueExpression intervalValueExpression = parseIntervalValueExpression();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new IntervalAbsoluteValueFunction(
                token.getBeginIndex(),
                token2.getEndIndex(),
                intervalValueExpression
        );
    }

    private ValueSpecification parseValueSpecification() {
        ValueSpecification valueSpecification = tryParseValueSpecification();
        if (valueSpecification != null) {
            return valueSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to value specification.");
    }

    private ValueSpecification tryParseValueSpecification() {
        Literal literal = tryParseLiteral();
        if (literal != null) {
            return literal;
        }
        GeneralValueSpecification generalValueSpecification
                = tryParseGeneralValueSpecification();
        if (generalValueSpecification != null) {
            return generalValueSpecification;
        }
        return null;
    }

    //   <host parameter specification>
    // | <SQL parameter reference>
    // | <dynamic parameter specification>
    // | <embedded variable specification>
    // | <current collation specification>
    // | CURRENT_DEFAULT_TRANSFORM_GROUP
    // | CURRENT_PATH
    // | CURRENT_ROLE
    // | CURRENT_TRANSFORM_GROUP_FOR_TYPE <path-resolved user-defined type name>
    // | CURRENT_USER
    // | SESSION_USER
    // | SYSTEM_USER
    // | USER
    // | VALUE
    private GeneralValueSpecification tryParseGeneralValueSpecification() {
        HostParameterSpecification hostParameterSpecification = tryParseHostParameterSpecification();
        if (hostParameterSpecification != null) {
            return hostParameterSpecification;
        }
        IdentifierChain sqlParameterReference = tryParseIdentifierChain();
        if (sqlParameterReference != null) {
            return sqlParameterReference;
        }
        DynamicParameterSpecification dynamicParameterSpecification = tryParseDynamicParameterSpecification();
        if (dynamicParameterSpecification != null) {
            return dynamicParameterSpecification;
        }
        EmbeddedVariableSpecification embeddedVariableSpecification = tryParseEmbeddedVariableSpecification();
        if (embeddedVariableSpecification != null) {
            return embeddedVariableSpecification;
        }
        CurrentCollationSpecification currentCollationSpecification = tryParseCurrentCollationSpecification();
        if (currentCollationSpecification != null) {
            return currentCollationSpecification;
        }
        CurrentPath currentPath = tryParseCurrentPath();
        if (currentPath != null) {
            return currentPath;
        }
        CurrentRole currentRole = tryParseCurrentRole();
        if (currentRole != null) {
            return currentRole;
        }
        CurrentUser currentUser = tryParseCurrentUser();
        if (currentUser != null) {
            return currentUser;
        }
        SessionUser sessionUser = tryParseSessionUser();
        if (sessionUser != null) {
            return sessionUser;
        }
        SystemUser systemUser = tryParseSystemUser();
        if (systemUser != null) {
            return systemUser;
        }
        User user = tryParseUser();
        if (user != null) {
            return user;
        }
        int markedIndex = mark();
        Token token = scan();
        if (token.isCurrentDefaultTransformGroup()) {
            return new CurrentDefaultTransformGroup(token.getBeginIndex(), token.getEndIndex());
        }
        if (token.isCurrentTransformGroupForType()) {
            SchemaQualifiedName pathResolvedUserDefinedTypeName
                     = parseSchemaQualifiedName();
            return new CurrentTransformGroupForType(
                    token.getBeginIndex(),
                    pathResolvedUserDefinedTypeName
            );
        }
        if (token.isValue()) {
            return new Value(token.getBeginIndex(), token.getEndIndex());
        }
        reset(markedIndex);
        return null;
    }

    // CURRENT_COLLATION <left paren> <string value expression> <right paren>
    private CurrentCollationSpecification tryParseCurrentCollationSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"CURRENT_COLLATION".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        StringValueExpression stringValueExpression = parseStringValueExpression();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new CurrentCollationSpecification(
                token.getBeginIndex(),
                token2.getEndIndex(),
                stringValueExpression
        );
    }

    // <embedded variable name> [ <indicator variable> ]
    private EmbeddedVariableSpecification tryParseEmbeddedVariableSpecification() {
        EmbeddedVariableName embeddedVariableName = tryParseEmbeddedVariableName();
        if (embeddedVariableName == null) {
            return null;
        }
        IndicatorVariable indicatorVariable = tryParseIndicatorVariable();
        return new EmbeddedVariableSpecification(embeddedVariableName, indicatorVariable);
    }

    private EmbeddedVariableName parseEmbeddedVariableName() {
        EmbeddedVariableName embeddedVariableName = tryParseEmbeddedVariableName();
        if (embeddedVariableName != null) {
            return embeddedVariableName;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to embedded variable name.");
    }

    // <colon> <host identifier>
    private EmbeddedVariableName tryParseEmbeddedVariableName() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isColon()) {
            reset(markedIndex);
            return null;
        }
        HostIdentifier hostIdentifier = parseHostIdentifier();
        return new EmbeddedVariableName(token.getBeginIndex(), hostIdentifier);
    }

    private HostIdentifier parseHostIdentifier() {
        HostIdentifier hostIdentifier = tryParseHostIdentifier();
        if (hostIdentifier != null) {
            return hostIdentifier;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to host identifier.");
    }

    //   <Ada host identifier>
    // | <C host identifier>
    // | <COBOL host identifier>
    // | <Fortran host identifier>
    // | <MUMPS host identifier>
    // | <Pascal host identifier>
    // | <PL/I host identifier>
    private HostIdentifier tryParseHostIdentifier() {
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Host identifier is not suppored.");
    }

    private IndicatorVariable tryParseIndicatorVariable() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isIndicator()) {
            EmbeddedVariableName embeddedVariableName = parseEmbeddedVariableName();
            return new IndicatorVariable(token.getBeginIndex(), true, embeddedVariableName);
        }
        reset(markedIndex);
        EmbeddedVariableName embeddedVariableName = tryParseEmbeddedVariableName();
        if (embeddedVariableName == null) {
            return null;
        }
        return new IndicatorVariable(token.getBeginIndex(), false, embeddedVariableName);
    }

    private DynamicParameterSpecification tryParseDynamicParameterSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isQuestionMark()) {
            reset(markedIndex);
            return null;
        }
        return new DynamicParameterSpecification(token.getBeginIndex(), token.getEndIndex());
    }

    private HostParameterSpecification tryParseHostParameterSpecification() {
        HostParameterName hostParameterName = tryParseHostParameterName();
        if (hostParameterName == null) {
            return null;
        }
        IndicatorParameter indicatorParameter = tryParseIndicatorParameter();
        return new HostParameterSpecification(hostParameterName, indicatorParameter);
    }

    // [ INDICATOR ] <host parameter name>
    private IndicatorParameter tryParseIndicatorParameter() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "INDICATOR".equalsIgnoreCase(token.getContent())) {
            HostParameterName hostParameterName = parseHostParameterName();
            return new IndicatorParameter(
                    token.getBeginIndex(),
                    true,
                    hostParameterName
            );
        }
        reset(markedIndex);
        HostParameterName hostParameterName = tryParseHostParameterName();
        if (hostParameterName == null) {
            return null;
        }
        return new IndicatorParameter(
                token.getBeginIndex(),
                false,
                hostParameterName
        );
    }

    private ConnectionObject parseConnectionObject() {
        ConnectionObject connectionObject = tryParseConnectionObject();
        if (connectionObject != null) {
            return connectionObject;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to connection object.");
    }

    private ConnectionObject tryParseConnectionObject() {
        int endIndex;
        boolean default_;
        SimpleValueSpecification connectionName;
        int markedIndex = mark();
        Token token = scan();
        if (token.isDefault()) {
            endIndex = token.getEndIndex();
            default_ = true;
            connectionName = null;
        } else {
            reset(markedIndex);
            connectionName = parseSimpleValueSpecification();
            endIndex = connectionName.getEndIndex();
            default_ = false;
        }
        return new ConnectionObject(token.getBeginIndex(), endIndex, default_, connectionName);
    }

    private DeleteStatement parseDeleteStatement() {
        DeleteStatement deleteStatement = tryParseDeleteStatement();
        if (deleteStatement != null) {
            return deleteStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to delete statement.");
    }

    private DeleteStatement tryParseDeleteStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDelete()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isFrom()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a FROM.");
        }
        TargetTable targetTable = parseTargetTable();
        int markedIndex2 = mark();
        Token token2 = scan();
        if (!token2.isWhere()) {
            reset(markedIndex2);
            return new DeleteStatement(token.getBeginIndex(), targetTable.getEndIndex(), targetTable);
        }
        int markedIndex3 = mark();
        Token token3 = scan();
        if (!token3.isCurrent()) {
            reset(markedIndex3);
            BooleanValueExpression searchCondition = parseBooleanValueExpression();
            return new DeleteStatement(token.getBeginIndex(), searchCondition.getEndIndex(), targetTable, searchCondition);
        }
        Token token4 = scan();
        if (!token4.isOf()) {
            throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a OF.");
        }
        DynamicCursorName dynamicCursorName = parseDynamicCursorName();
        return new DeleteStatement(token.getBeginIndex(), dynamicCursorName.getEndIndex(), targetTable, dynamicCursorName);
    }

    private UpdateStatement parseUpdateStatement() {
        UpdateStatement updateStatement = tryParseUpdateStatement();
        if (updateStatement != null) {
            return updateStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to update statement.");
    }

    private UpdateStatement tryParseUpdateStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isUpdate()) {
            reset(markedIndex);
            return null;
        }
        TargetTable targetTable = parseTargetTable();
        Token token1 = scan();
        if (!token1.isSet()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SET.");
        }
        SetClauseList setClauseList = parseSetClauseList();
        int markedIndex2 = mark();
        Token token2 = scan();
        if (!token2.isWhere()) {
            reset(markedIndex2);
            return new UpdateStatement(token.getBeginIndex(), setClauseList.get(setClauseList.size() - 1).getEndIndex(), targetTable, setClauseList);
        }
        int markedIndex3 = mark();
        Token token3 = scan();
        if (!token3.isCurrent()) {
            reset(markedIndex3);
            BooleanValueExpression searchCondition = parseBooleanValueExpression();
            return new UpdateStatement(token.getBeginIndex(), searchCondition.getEndIndex(), targetTable, setClauseList, searchCondition);
        }
        Token token4 = scan();
        if (!token4.isOf()) {
            throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a OF.");
        }
        DynamicCursorName dynamicCursorName = parseDynamicCursorName();
        return new UpdateStatement(token.getBeginIndex(), dynamicCursorName.getEndIndex(), targetTable, setClauseList, dynamicCursorName);
    }

    private SavepointStatement tryParseSavepointStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSavepoint()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() && !token1.isUnicodeDelimiterBody()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a savepoint specifier.");
        }
        Identifier savepointSpecifier = new Identifier(token1);
        return new SavepointStatement(token1.getBeginIndex(), savepointSpecifier);
    }

    private DescribeStatement parseDescribeStatement() {
        DescribeStatement describeStatement = tryParseDescribeStatement();
        if (describeStatement != null) {
            return describeStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to describe statement.");
    }

    private SystemDescriptorStatement parseSystemDescriptorStatement() {
        SystemDescriptorStatement systemDescriptorStatement
                = tryParseSystemDescriptorStatement();
        if (systemDescriptorStatement != null) {
            return systemDescriptorStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to system descriptor statement.");
    }

    //   <allocate descriptor statement>
    // | <deallocate descriptor statement>
    // | <set descriptor statement>
    // | <get descriptor statement>
    private SystemDescriptorStatement tryParseSystemDescriptorStatement() {
        AllocateDescriptorStatement allocateDescriptorStatement = tryParseAllocateDescriptorStatement();
        if (allocateDescriptorStatement != null) {
            return allocateDescriptorStatement;
        }
        DeallocateDescriptorStatement deallocateDescriptorStatement = tryParseDeallocateDescriptorStatement();
        if (deallocateDescriptorStatement != null) {
            return deallocateDescriptorStatement;
        }
        SetDescriptorStatement setDescriptorStatement = tryParseSetDescriptorStatement();
        if (setDescriptorStatement != null) {
            return setDescriptorStatement;
        }
        GetDescriptorStatement getDescriptorStatement = tryParseGetDescriptorStatement();
        if (getDescriptorStatement != null) {
            return getDescriptorStatement;
        }
        return null;
    }

    // ALLOCATE [ SQL ] DESCRIPTOR <descriptor name> [ WITH MAX <occurrences> ]
    private AllocateDescriptorStatement tryParseAllocateDescriptorStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAllocate()) {
            reset(markedIndex);
            return null;
        }
        boolean sql = false;
        Token token1 = scan();
        if (token1.isSql()) {
            sql = true;
            token1 = scan();
        }
        if (!token1.isRegularIdentifier() || !"DESCRIPTOR".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a DESCRIPTOR.");
        }
        DescriptorName descriptorName = parseDescriptorName();
        int endIndex = descriptorName.getEndIndex();
        SimpleValueSpecification occurrences = null;
        int markedIndex2 = mark();
        Token token2 = scan();
        if (token2.isWith()) {
            Token token3 = scan();
            if (!token3.isRegularIdentifier() || !"MAX".equalsIgnoreCase(token3.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a MAX.");
            }
            occurrences = parseSimpleValueSpecification();
            endIndex = occurrences.getEndIndex();
        } else {
            reset(markedIndex2);
        }
        return new AllocateDescriptorStatement(token.getBeginIndex(), endIndex, sql, descriptorName, occurrences);
    }

    // [ <scope option> ] <simple value specification>
    private DescriptorName parseDescriptorName() {
        ScopeOption scopeOption = tryParseScopeOption();
        SimpleValueSpecification simpleValueSpecification = parseSimpleValueSpecification();
        int beginIndex;
        if (scopeOption != null) {
            beginIndex = scopeOption.getBeginIndex();
        } else {
            beginIndex = simpleValueSpecification.getBeginIndex();
        }
        return new DescriptorName(beginIndex, scopeOption, simpleValueSpecification);
    }

    // DEALLOCATE [ SQL ] DESCRIPTOR <descriptor name>
    private DeallocateDescriptorStatement tryParseDeallocateDescriptorStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDeallocate()) {
            reset(markedIndex);
            return null;
        }
        boolean sql = false;
        Token token1 = scan();
        if (token1.isSql()) {
            sql = true;
            token1 = scan();
        }
        if (!token1.isRegularIdentifier() || !"DESCRIPTOR".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a DESCRIPTOR.");
        }
        DescriptorName descriptorName = parseDescriptorName();
        return new DeallocateDescriptorStatement(token.getBeginIndex(), sql, descriptorName);
    }

    // SET [ SQL ] DESCRIPTOR <descriptor name> <set descriptor information>
    private SetDescriptorStatement tryParseSetDescriptorStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            reset(markedIndex);
            return null;
        }
        boolean sql = false;
        Token token1 = scan();
        if (token1.isSql()) {
            sql = true;
            token1 = scan();
        }
        if (!token1.isRegularIdentifier() || !"DESCRIPTOR".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a DESCRIPTOR.");
        }
        DescriptorName descriptorName = parseDescriptorName();
        SetDescriptorInformation setDescriptorInformation = parseSetDescriptorInformation();
        return new SetDescriptorStatement(token.getBeginIndex(), sql, descriptorName, setDescriptorInformation);
    }

    //   <set header information> [ { <comma> <set header information> }... ]
    // | VALUE <item number> <set item information> [ { <comma> <set item information> }... ]
    private SetDescriptorInformation parseSetDescriptorInformation() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isValue()) {
            SimpleValueSpecification itemNumber = parseSimpleValueSpecification();
            SetItemInformation setItemInformation = parseSetItemInformation();
            List<SetItemInformation> setItemInformationList = new ArrayList<>();
            setItemInformationList.add(setItemInformation);
            while (true) {
                int markedIndex1 = mark();
                Token token1 = scan();
                if (!token1.isComma()) {
                    reset(markedIndex1);
                    break;
                }
                SetItemInformation setItemInformation1 = parseSetItemInformation();
                setItemInformationList.add(setItemInformation1);
            }
            return new SetDescriptorInformation(
                    token.getBeginIndex(),
                    itemNumber,
                    new SetItemInformationList(setItemInformationList)
            );
        }
        reset(markedIndex);
        SetHeaderInformation setHeaderInformation = parseSetHeaderInformation();
        List<SetHeaderInformation> setHeaderInformationList = new ArrayList<>();
        setHeaderInformationList.add(setHeaderInformation);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            SetHeaderInformation setHeaderInformation1 = parseSetHeaderInformation();
            setHeaderInformationList.add(setHeaderInformation1);
        }
        return new SetDescriptorInformation(new SetHeaderInformationList(setHeaderInformationList));
    }

    private SetItemInformation parseSetItemInformation() {
        DescriptorItemName descriptorItemName = parseDescriptorItemName();
        Token token = scan();
        if (!token.isEqualsOperator()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a =.");
        }
        SimpleValueSpecification simpleValueSpecification2 = parseSimpleValueSpecification();
        return new SetItemInformation(descriptorItemName, simpleValueSpecification2);
    }

    //   CARDINALITY
    // | CHARACTER_SET_CATALOG
    // | CHARACTER_SET_NAME
    // | CHARACTER_SET_SCHEMA
    // | COLLATION_CATALOG
    // | COLLATION_NAME
    // | COLLATION_SCHEMA
    // | DATA
    // | DATETIME_INTERVAL_CODE
    // | DATETIME_INTERVAL_PRECISION
    // | DEGREE
    // | INDICATOR
    // | KEY_MEMBER
    // | LENGTH
    // | LEVEL
    // | NAME
    // | NULLABLE
    // | OCTET_LENGTH
    // | PARAMETER_MODE
    // | PARAMETER_ORDINAL_POSITION
    // | PARAMETER_SPECIFIC_CATALOG
    // | PARAMETER_SPECIFIC_NAME
    // | PARAMETER_SPECIFIC_SCHEMA
    // | PRECISION
    // | RETURNED_CARDINALITY
    // | RETURNED_LENGTH
    // | RETURNED_OCTET_LENGTH
    // | SCALE
    // | SCOPE_CATALOG
    // | SCOPE_NAME
    // | SCOPE_SCHEMA
    // | TYPE
    // | UNNAMED
    // | USER_DEFINED_TYPE_CATALOG
    // | USER_DEFINED_TYPE_NAME
    // | USER_DEFINED_TYPE_SCHEMA
    // | USER_DEFINED_TYPE_CODE
    private DescriptorItemName parseDescriptorItemName() {
        Token token = scan();
        if (token.isRegularIdentifier()) {
            if ("CARDINALITY".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.CARDINALITY);
            }
            if ("CHARACTER_SET_CATALOG".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.CHARACTER_SET_CATALOG);
            }
            if ("CHARACTER_SET_NAME".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.CHARACTER_SET_NAME);
            }
            if ("CHARACTER_SET_SCHEMA".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.CHARACTER_SET_SCHEMA);
            }
            if ("COLLATION_CATALOG".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.COLLATION_CATALOG);
            }
            if ("COLLATION_NAME".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.COLLATION_NAME);
            }
            if ("COLLATION_SCHEMA".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.COLLATION_SCHEMA);
            }
            if ("DATA".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.DATA);
            }
            if ("DATETIME_INTERVAL_CODE".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.DATETIME_INTERVAL_CODE);
            }
            if ("DATETIME_INTERVAL_PRECISION".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.DATETIME_INTERVAL_PRECISION);
            }
            if ("DEGREE".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.DEGREE);
            }
            if ("KEY_MEMBER".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.KEY_MEMBER);
            }
            if ("LENGTH".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.LENGTH);
            }
            if ("LEVEL".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.LEVEL);
            }
            if ("NAME".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.NAME);
            }
            if ("NULLABLE".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.NULLABLE);
            }
            if ("OCTET_LENGTH".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.OCTET_LENGTH);
            }
            if ("PARAMETER_MODE".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.PARAMETER_MODE);
            }
            if ("PARAMETER_ORDINAL_POSITION".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.PARAMETER_ORDINAL_POSITION);
            }
            if ("PARAMETER_SPECIFIC_CATALOG".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.PARAMETER_SPECIFIC_CATALOG);
            }
            if ("PARAMETER_SPECIFIC_NAME".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.PARAMETER_SPECIFIC_NAME);
            }
            if ("PARAMETER_SPECIFIC_SCHEMA".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.PARAMETER_SPECIFIC_SCHEMA);
            }
            if ("PRECISION".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.PRECISION);
            }
            if ("RETURNED_CARDINALITY".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.RETURNED_CARDINALITY);
            }
            if ("RETURNED_LENGTH".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.RETURNED_LENGTH);
            }
            if ("RETURNED_OCTET_LENGTH".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.RETURNED_OCTET_LENGTH);
            }
            if ("SCALE".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.SCALE);
            }
            if ("SCOPE_CATALOG".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.SCOPE_CATALOG);
            }
            if ("SCOPE_NAME".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.SCOPE_NAME);
            }
            if ("SCOPE_SCHEMA".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.SCOPE_SCHEMA);
            }
            if ("TYPE".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.TYPE);
            }
            if ("UNNAMED".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.UNNAMED);
            }
            if ("USER_DEFINED_TYPE_CATALOG".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.USER_DEFINED_TYPE_CATALOG);
            }
            if ("USER_DEFINED_TYPE_NAME".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.USER_DEFINED_TYPE_NAME);
            }
            if ("USER_DEFINED_TYPE_SCHEMA".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.USER_DEFINED_TYPE_SCHEMA);
            }
            if ("USER_DEFINED_TYPE_CODE".equalsIgnoreCase(token.getContent())) {
                return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.USER_DEFINED_TYPE_CODE);
            }
        }
        if (token.isIndicator()) {
            return new DescriptorItemName(token.getBeginIndex(), token.getBeginIndex(), DescriptorItemNameType.INDICATOR);
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to descriptor item name.");
    }

    private SetHeaderInformation parseSetHeaderInformation() {
        HeaderItemName headerItemName = parseHeaderItemName();
        Token token = scan();
        if (!token.isEqualsOperator()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a =.");
        }
        SimpleValueSpecification simpleValueSpecification1 = parseSimpleValueSpecification();
        return new SetHeaderInformation(headerItemName, simpleValueSpecification1);
    }

    // COUNT | KEY_TYPE | DYNAMIC_FUNCTION | DYNAMIC_FUNCTION_CODE | TOP_LEVEL_COUNT
    private HeaderItemName parseHeaderItemName() {
        Token token = scan();
        if (token.isRegularIdentifier()) {
            if ("COUNT".equalsIgnoreCase(token.getContent())) {
                return new HeaderItemName(token.getBeginIndex(), token.getEndIndex(), HeaderItemNameType.COUNT);
            }
            if ("KEY_TYPE".equalsIgnoreCase(token.getContent())) {
                return new HeaderItemName(token.getBeginIndex(), token.getEndIndex(), HeaderItemNameType.KEY_TYPE);
            }
            if ("DYNAMIC_FUNCTION".equalsIgnoreCase(token.getContent())) {
                return new HeaderItemName(token.getBeginIndex(), token.getEndIndex(), HeaderItemNameType.DYNAMIC_FUNCTION);
            }
            if ("DYNAMIC_FUNCTION_CODE".equalsIgnoreCase(token.getContent())) {
                return new HeaderItemName(token.getBeginIndex(), token.getEndIndex(), HeaderItemNameType.DYNAMIC_FUNCTION_CODE);
            }
            if ("TOP_LEVEL_COUNT".equalsIgnoreCase(token.getContent())) {
                return new HeaderItemName(token.getBeginIndex(), token.getEndIndex(), HeaderItemNameType.TOP_LEVEL_COUNT);
            }
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to header item name.");
    }

    // GET [ SQL ] DESCRIPTOR <descriptor name> <get descriptor information>
    private GetDescriptorStatement tryParseGetDescriptorStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isGet()) {
            reset(markedIndex);
            return null;
        }
        boolean sql = false;
        Token token1 = scan();
        if (token1.isSql()) {
            sql = true;
            token1 = scan();
        }
        if (!token1.isRegularIdentifier() || !"DESCRIPTOR".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a DESCRIPTOR.");
        }
        DescriptorName descriptorName = parseDescriptorName();
        GetDescriptorInformation getDescriptorInformation = parseGetDescriptorInformation();
        return new GetDescriptorStatement(token.getBeginIndex(), sql, descriptorName, getDescriptorInformation);
    }

    //   <get header information> [ { <comma> <get header information> }... ]
    // | VALUE <item number> <get item information> [ { <comma> <get item information> }... ]
    private GetDescriptorInformation parseGetDescriptorInformation() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isValue()) {
            SimpleValueSpecification itemNumber = parseSimpleValueSpecification();
            GetItemInformation getItemInformation = parseGetItemInformation();
            List<GetItemInformation> getItemInformationList = new ArrayList<>();
            getItemInformationList.add(getItemInformation);
            while (true) {
                int markedIndex1 = mark();
                Token token1 = scan();
                if (!token1.isComma()) {
                    reset(markedIndex1);
                    break;
                }
                GetItemInformation getItemInformation1 = parseGetItemInformation();
                getItemInformationList.add(getItemInformation1);
            }
            return new GetDescriptorInformation(
                    token.getBeginIndex(),
                    itemNumber,
                    new GetItemInformationList(getItemInformationList)
            );
        }
        reset(markedIndex);
        GetHeaderInformation getHeaderInformation = parseGetHeaderInformation();
        List<GetHeaderInformation> getHeaderInformationList = new ArrayList<>();
        getHeaderInformationList.add(getHeaderInformation);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            GetHeaderInformation getHeaderInformation1 = parseGetHeaderInformation();
            getHeaderInformationList.add(getHeaderInformation1);
        }
        return new GetDescriptorInformation(
                new GetHeaderInformationList(getHeaderInformationList)
        );
    }

    // <simple target specification 2> <equals operator> <descriptor item name>
    private GetItemInformation parseGetItemInformation() {
        SimpleTargetSpecification simpleTargetSpecification2 = parseSimpleTargetSpecification();
        Token token = scan();
        if (!token.isEqualsOperator()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a =.");
        }
        DescriptorItemName descriptorItemName = parseDescriptorItemName();
        return new GetItemInformation(simpleTargetSpecification2, descriptorItemName);
    }

    // <simple target specification 1> <equals operator> <header item name>
    private GetHeaderInformation parseGetHeaderInformation() {
        SimpleTargetSpecification simpleTargetSpecification1 = parseSimpleTargetSpecification();
        Token token = scan();
        if (!token.isEqualsOperator()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a =.");
        }
        HeaderItemName headerItemName = parseHeaderItemName();
        return new GetHeaderInformation(simpleTargetSpecification1, headerItemName);
    }

    private ExecuteStatement parseExecuteStatement() {
        ExecuteStatement executeStatement = tryParseExecuteStatement();
        if (executeStatement != null) {
            return executeStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to execute statement.");
    }

    // EXECUTE <SQL statement name> [ <result using clause> ] [ <parameter using clause> ]
    private ExecuteStatement tryParseExecuteStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isExecute()) {
            reset(markedIndex);
            return null;
        }
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isImmediate()) {
            reset(markedIndex);
            return null;
        }
        reset(markedIndex1);
        SqlStatementName sqlStatementName = parseSqlStatementName();
        OutputUsingClause resultUsingClause = tryParseOutputUsingClause();
        InputUsingClause parameterUsingClause = tryParseInputUsingClause();
        int endIndex = sqlStatementName.getEndIndex();
        if (resultUsingClause != null) {
            endIndex = resultUsingClause.getEndIndex();
        }
        if (parameterUsingClause != null) {
            endIndex = parameterUsingClause.getEndIndex();
        }
        return new ExecuteStatement(token.getBeginIndex(), endIndex, sqlStatementName, resultUsingClause, parameterUsingClause);
    }

    private OutputUsingClause parseOutputUsingClause() {
        OutputUsingClause outputUsingClause = tryParseOutputUsingClause();
        if (outputUsingClause != null) {
            return outputUsingClause;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to output using clause.");
    }

    // <output using clause> ::= <into arguments> | <into descriptor>
    private OutputUsingClause tryParseOutputUsingClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isInto()) {
            reset(markedIndex);
            return null;
        }
        boolean sql = false;
        DescriptorName descriptorName = null;
        List<TargetSpecification> intoArgumentList = null;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isSql()) {
            sql = true;
            Token token2 = scan();
            if (!token2.isRegularIdentifier() || !"DESCRIPTOR".equalsIgnoreCase(token2.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a DESCRIPTOR.");
            }
            descriptorName = parseDescriptorName();
        } else if (token1.isRegularIdentifier() && "DESCRIPTOR".equalsIgnoreCase(token1.getContent())) {
            descriptorName = parseDescriptorName();
        } else {
            reset(markedIndex1);
            TargetSpecification intoArgument = parseTargetSpecification();
            intoArgumentList = new ArrayList<>();
            intoArgumentList.add(intoArgument);
            while (true) {
                int markedIndex2 = mark();
                Token token2 = scan();
                if (!token2.isComma()) {
                    reset(markedIndex2);
                    break;
                }
                TargetSpecification intoArgument1 = parseTargetSpecification();
                intoArgumentList.add(intoArgument1);
            }
        }
        if (descriptorName != null) {
            return new OutputUsingClause(token.getBeginIndex(), sql, descriptorName);
        }
        return new OutputUsingClause(token.getBeginIndex(), intoArgumentList);
    }

    private InputUsingClause tryParseInputUsingClause() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isUsing()) {
            GeneralValueSpecification usingArgument = tryParseGeneralValueSpecification();
            List<GeneralValueSpecification> usingArgumentList = new ArrayList<>();
            usingArgumentList.add(usingArgument);
            while (true) {
                int markedIndex1 = mark();
                Token token1 = scan();
                if (!token1.isComma()) {
                    reset(markedIndex1);
                    break;
                }
                GeneralValueSpecification usingArgument1 = tryParseGeneralValueSpecification();
                usingArgumentList.add(usingArgument1);
            }
            return new InputUsingClause(token.getBeginIndex(), usingArgumentList);
        }
        reset(markedIndex);
        UsingDescriptor usingDescriptor = tryParseUsingDescriptor();
        if (usingDescriptor != null) {
            return new InputUsingClause(usingDescriptor);
        }
        return null;
    }

    private FetchStatement parseFetchStatement() {
        FetchStatement fetchStatement = tryParseFetchStatement();
        if (fetchStatement != null) {
            return fetchStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to fetch statement.");
    }

    // FETCH [ [ <fetch orientation> ] FROM ] <cursor name> INTO <fetch target list>
    private FetchStatement tryParseFetchStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isFetch()) {
            reset(markedIndex);
            return null;
        }
        FetchOrientation fetchOrientation = tryParseFetchOrientation();
        boolean from;
        if (fetchOrientation == null) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isFrom()) {
                reset(markedIndex1);
                from = false;
            } else {
                from = true;
            }
        } else {
            Token token1 = scan();
            if (!token1.isFrom()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a FROM.");
            }
            from = true;
        }
        LocalQualifiedName cursorName = parseLocalQualifiedName();
        Token token1 = scan();
        if (!token1.isInto()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a INTO.");
        }
        TargetSpecificationList fetchTargetList = parseTargetSpecificationList();
        int endIndex = fetchTargetList.get(fetchTargetList.size() - 1).getEndIndex();
        return new FetchStatement(token.getBeginIndex(), endIndex, fetchOrientation, from, cursorName, fetchTargetList);
    }

    //   NEXT
    // | PRIOR
    // | FIRST
    // | LAST
    // | { ABSOLUTE | RELATIVE } <simple value specification>
    private FetchOrientation tryParseFetchOrientation() {
        boolean next = false;
        boolean prior = false;
        boolean first = false;
        boolean last = false;
        boolean absolute = false;
        boolean relative = false;
        SimpleValueSpecification simpleValueSpecification = null;
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "NEXT".equalsIgnoreCase(token.getContent())) {
            next = true;
        } else if (token.isRegularIdentifier() && "PRIOR".equalsIgnoreCase(token.getContent())) {
            prior = true;
        } else if (token.isRegularIdentifier() && "FIRST".equalsIgnoreCase(token.getContent())) {
            first = true;
        } else if (token.isRegularIdentifier() && "LAST".equalsIgnoreCase(token.getContent())) {
            last = true;
        } else if (token.isRegularIdentifier() && "absolute".equalsIgnoreCase(token.getContent())) {
            absolute = true;
            simpleValueSpecification = parseSimpleValueSpecification();
        } else if (token.isRegularIdentifier() && "relative".equalsIgnoreCase(token.getContent())) {
            relative = true;
            simpleValueSpecification = parseSimpleValueSpecification();
        } else {
            reset(markedIndex);
            return null;
        }
        int endIndex = token.getEndIndex();
        if (simpleValueSpecification != null) {
            endIndex = simpleValueSpecification.getEndIndex();
        }
        return new FetchOrientation(token.getBeginIndex(), endIndex, next, prior, first, last, absolute, relative, simpleValueSpecification);
    }

    private TargetSpecificationList parseTargetSpecificationList() {
        TargetSpecification targetSpecification = parseTargetSpecification();
        List<TargetSpecification> targetSpecificationList = new ArrayList<>();
        targetSpecificationList.add(targetSpecification);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isComma()) {
                reset(markedIndex);
                break;
            }
            TargetSpecification targetSpecification1 = parseTargetSpecification();
            targetSpecificationList.add(targetSpecification1);
        }
        return new TargetSpecificationList(targetSpecificationList);
    }

    private TargetSpecification parseTargetSpecification() {
        TargetSpecification targetSpecification = tryParseTargetSpecification();
        if (targetSpecification != null) {
            return targetSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to target specification.");
    }

    //   <host parameter specification>
    // | <SQL parameter reference>
    // | <column reference>
    // | <target array element specification>
    // | <dynamic parameter specification>
    // | <embedded variable specification>
    private TargetSpecification tryParseTargetSpecification() {
        HostParameterSpecification hostParameterSpecification
                = tryParseHostParameterSpecification();
        if (hostParameterSpecification != null) {
            return hostParameterSpecification;
        }
        IdentifierChain sqlParameterReference = tryParseIdentifierChain();
        if (sqlParameterReference != null) {
            return sqlParameterReference;
        }
        ColumnReference columnReference = tryParseColumnReference();
        if (columnReference != null) {
            return columnReference;
        }
        TargetArrayElementSpecification targetArrayElementSpecification
                = tryParseTargetArrayElementSpecification();
        if (targetArrayElementSpecification != null) {
            return targetArrayElementSpecification;
        }
        DynamicParameterSpecification dynamicParameterSpecification
                = tryParseDynamicParameterSpecification();
        if (dynamicParameterSpecification != null) {
            return dynamicParameterSpecification;
        }
        EmbeddedVariableSpecification embeddedVariableSpecification
                = tryParseEmbeddedVariableSpecification();
        if (embeddedVariableSpecification != null) {
            return embeddedVariableSpecification;
        }
        return null;
    }

    // <target array reference> <left bracket or trigraph> <simple value specification> <right bracket or trigraph>
    private TargetArrayElementSpecification tryParseTargetArrayElementSpecification() {
        TargetArrayReference targetArrayReference = tryParseTargetArrayReference();
        if (targetArrayReference == null) {
            return null;
        }
        Token token = scan();
        if (!token.isLeftBracket() && !token.isLeftBracketTrigraph()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a [ or ??(.");
        }
        SimpleValueSpecification simpleValueSpecification = parseSimpleValueSpecification();
        Token token1 = scan();
        if (!token1.isRightBracket() && !token1.isRightBracketTrigraph()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ] or ??).");
        }
        return new TargetArrayElementSpecification(
                token1.getBeginIndex(),
                targetArrayReference,
                simpleValueSpecification
        );
    }

    // <SQL parameter reference> | <column reference>
    private TargetArrayReference tryParseTargetArrayReference() {
        IdentifierChain sqlParameterReference = tryParseIdentifierChain();
        if (sqlParameterReference != null) {
            return sqlParameterReference;
        }
        ColumnReference columnReference = tryParseColumnReference();
        if (columnReference != null) {
            return columnReference;
        }
        return null;
    }

    private GetDiagnosticsStatement parseGetDiagnosticsStatement() {
        GetDiagnosticsStatement getDiagnosticsStatement = tryParseGetDiagnosticsStatement();
        if (getDiagnosticsStatement != null) {
            return getDiagnosticsStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to get diagnostics statement.");
    }

    // GET DIAGNOSTICS <SQL diagnostics information>
    private GetDiagnosticsStatement tryParseGetDiagnosticsStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isGet()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"DIAGNOSTICS".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a DIAGNOSTICS.");
        }
        SqlDiagnosticsInformation sqlDiagnosticsInformation = parseSqlDiagnosticsInformation();
        return new GetDiagnosticsStatement(token.getBeginIndex(), sqlDiagnosticsInformation);
    }

    // <statement information> | <condition information>
    private SqlDiagnosticsInformation parseSqlDiagnosticsInformation() {
        StatementInformation statementInformation = tryParseStatementInformation();
        if (statementInformation != null) {
            return statementInformation;
        }
        ConditionInformation conditionInformation = tryParseConditionInformation();
        if (conditionInformation != null) {
            return conditionInformation;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to SQL diagnostics information.");
    }

    // <statement information item> [ { <comma> <statement information item> }... ]
    private StatementInformation tryParseStatementInformation() {
        StatementInformationItem statementInformationItem = tryParseStatementInformationItem();
        if (statementInformationItem == null) {
            return null;
        }
        List<StatementInformationItem> statementInformationItemList = new ArrayList<>();
        statementInformationItemList.add(statementInformationItem);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isComma()) {
                reset(markedIndex);
                break;
            }
            StatementInformationItem statementInformationItem1 = parseStatementInformationItem();
            statementInformationItemList.add(statementInformationItem1);
        }
        return new StatementInformation(
                statementInformationItemList
        );
    }

    private StatementInformationItem parseStatementInformationItem() {
        StatementInformationItem statementInformationItem = tryParseStatementInformationItem();
        if (statementInformationItem != null) {
            return statementInformationItem;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to statement information item.");
    }

    // <simple target specification> <equals operator> <statement information item name>
    private StatementInformationItem tryParseStatementInformationItem() {
        SimpleTargetSpecification simpleTargetSpecification = parseSimpleTargetSpecification();
        Token token = scan();
        if (!token.isEqualsOperator()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a =.");
        }
        StatementInformationItemName statementInformationItemName = parseStatementInformationItemName();
        return new StatementInformationItem(simpleTargetSpecification, statementInformationItemName);
    }

    //   <host parameter specification>
    // | <SQL parameter reference>
    // | <column reference>
    // | <embedded variable name>
    private SimpleTargetSpecification parseSimpleTargetSpecification() {
        HostParameterSpecification hostParameterSpecification = tryParseHostParameterSpecification();
        if (hostParameterSpecification != null) {
            return hostParameterSpecification;
        }
        IdentifierChain sqlParameterReference = tryParseIdentifierChain();
        if (sqlParameterReference != null) {
            return sqlParameterReference;
        }
        ColumnReference columnReference = tryParseColumnReference();
        if (columnReference != null) {
            return columnReference;
        }
        EmbeddedVariableName embeddedVariableName = tryParseEmbeddedVariableName();
        if (embeddedVariableName != null) {
            return embeddedVariableName;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to simple target specification.");
    }

    // { EXCEPTION | CONDITION } <condition number> <condition information item> [ { <comma> <condition information item> }... ]
    private ConditionInformation tryParseConditionInformation() {
        boolean exception = false;
        boolean condition = false;
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "EXCEPTION".equalsIgnoreCase(token.getContent())) {
            exception = true;
        } else if (token.isRegularIdentifier() && "CONDITION".equalsIgnoreCase(token.getContent())) {
            condition = true;
        } else {
            reset(markedIndex);
            return null;
        }
        SimpleValueSpecification conditionNumber = parseSimpleValueSpecification();
        ConditionInformationItem conditionInformationItem = parseConditionInformationItem();
        List<ConditionInformationItem> conditionInformationItemList = new ArrayList<>();
        conditionInformationItemList.add(conditionInformationItem);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            ConditionInformationItem conditionInformationItem1 = parseConditionInformationItem();
            conditionInformationItemList.add(conditionInformationItem1);
        }
        return new ConditionInformation(
                token.getBeginIndex(),
                exception,
                condition,
                conditionNumber,
                new ConditionInformationItemList(conditionInformationItemList)
        );
    }

    // <simple target specification> <equals operator> <condition information item name>
    private ConditionInformationItem parseConditionInformationItem() {
        SimpleTargetSpecification simpleTargetSpecification = parseSimpleTargetSpecification();
        Token token = scan();
        if (!token.isEqualsOperator()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a =.");
        }
        ConditionInformationItemName conditionInformationItemName = parseConditionInformationItemName();
        return new ConditionInformationItem(simpleTargetSpecification, conditionInformationItemName);
    }

    private StatementInformationItemName parseStatementInformationItemName() {
        StatementInformationItemName statementInformationItemName
            = tryParseStatementInformationItemName();
        if (statementInformationItemName != null) {
            return statementInformationItemName;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to statement information item name.");
    }

    //   NUMBER
    // | MORE
    // | COMMAND_FUNCTION
    // | COMMAND_FUNCTION_CODE
    // | DYNAMIC_FUNCTION
    // | DYNAMIC_FUNCTION_CODE
    // | ROW_COUNT
    // | TRANSACTIONS_COMMITTED
    // | TRANSACTIONS_ROLLED_BACK
    // | TRANSACTION_ACTIVE
    private StatementInformationItemName tryParseStatementInformationItemName() {
        boolean number = false;
        boolean more = false;
        boolean commandFunction = false;
        boolean commandFunctionCode = false;
        boolean dynamicFunction = false;
        boolean dynamicFunctionCode = false;
        boolean rowCount = false;
        boolean transactionsCommitted = false;
        boolean transactionsRolledBack = false;
        boolean transactionActive = false;
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "NUMBER".equalsIgnoreCase(token.getContent())) {
            number = true;
        } else if (token.isRegularIdentifier() && "MORE".equalsIgnoreCase(token.getContent())) {
            more = true;
        } else if (token.isRegularIdentifier() && "COMMAND_FUNCTION".equals(token.getContent())) {
            commandFunction = true;
        } else if (token.isRegularIdentifier() && "COMMAND_FUNCTION_CODE".equals(token.getContent())) {
            commandFunctionCode = true;
        } else if (token.isRegularIdentifier() && "DYNAMIC_FUNCTION".equals(token.getContent())) {
            dynamicFunction = true;
        } else if (token.isRegularIdentifier() && "DYNAMIC_FUNCTION_CODE".equals(token.getContent())) {
            dynamicFunctionCode = true;
        } else if (token.isRegularIdentifier() && "ROW_COUNT".equals(token.getContent())) {
            rowCount = true;
        } else if (token.isRegularIdentifier() && "TRANSACTIONS_COMMITTED".equals(token.getContent())) {
            transactionsCommitted = true;
        } else if (token.isRegularIdentifier() && "TRANSACTIONS_ROLLED_BACK".equals(token.getContent())) {
            transactionsRolledBack = true;
        } else if (token.isRegularIdentifier() && "TRANSACTION_ACTIVE".equals(token.getContent())) {
            transactionActive = true;
        } else {
            reset(markedIndex);
            return null;
        }
        return new StatementInformationItemName(
            token.getBeginIndex(),
            token.getEndIndex(),
            number,
            more,
            commandFunction,
            commandFunctionCode,
            dynamicFunction,
            dynamicFunctionCode,
            rowCount,
            transactionsCommitted,
            transactionsRolledBack,
            transactionActive
        );
    }

    private ConditionInformationItemName parseConditionInformationItemName() {
        ConditionInformationItemName conditionInformationItemName
            = tryParseConditionInformationItemName();
        if (conditionInformationItemName != null) {
            return conditionInformationItemName;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to condition information item name.");
    }

    //   CATALOG_NAME
    // | CLASS_ORIGIN
    // | COLUMN_NAME
    // | CONDITION_NUMBER
    // | CONNECTION_NAME
    // | CONSTRAINT_CATALOG
    // | CONSTRAINT_NAME
    // | CONSTRAINT_SCHEMA
    // | CURSOR_NAME
    // | MESSAGE_LENGTH
    // | MESSAGE_OCTET_LENGTH
    // | MESSAGE_TEXT
    // | PARAMETER_MODE
    // | PARAMETER_NAME
    // | PARAMETER_ORDINAL_POSITION
    // | RETURNED_SQLSTATE
    // | ROUTINE_CATALOG
    // | ROUTINE_NAME
    // | ROUTINE_SCHEMA
    // | SCHEMA_NAME
    // | SERVER_NAME
    // | SPECIFIC_NAME
    // | SUBCLASS_ORIGIN
    // | TABLE_NAME
    // | TRIGGER_CATALOG
    // | TRIGGER_NAME
    // | TRIGGER_SCHEMA
    private ConditionInformationItemName tryParseConditionInformationItemName() {
        boolean catalogName= false;
        boolean classOrigin= false;
        boolean columnName= false;
        boolean conditionNumber= false;
        boolean connectionName= false;
        boolean constraintCatalog= false;
        boolean constraintName= false;
        boolean constraintSchema= false;
        boolean cursorName= false;
        boolean messageLength= false;
        boolean messageOctetLength= false;
        boolean messageText= false;
        boolean parameterMode= false;
        boolean parameterName= false;
        boolean parameterOrdinalPosition= false;
        boolean returnedSqlstate= false;
        boolean routineCatalog= false;
        boolean routineName= false;
        boolean routineSchema= false;
        boolean schemaName= false;
        boolean serverName= false;
        boolean specificName= false;
        boolean subclassOrigin= false;
        boolean tableName= false;
        boolean triggerCatalog= false;
        boolean triggerName= false;
        boolean triggerSchema= false;
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "CATALOG_NAME".equalsIgnoreCase(token.getContent())) {
            catalogName = true;
        } else if (token.isRegularIdentifier() && "CLASS_ORIGIN".equalsIgnoreCase(token.getContent())) {
            classOrigin = true;
        } else if (token.isRegularIdentifier() && "COLUMN_NAME".equalsIgnoreCase(token.getContent())) {
            columnName = true;
        } else if (token.isRegularIdentifier() && "CONDITION_NUMBER".equalsIgnoreCase(token.getContent())) {
            conditionNumber = true;
        } else if (token.isRegularIdentifier() && "CONNECTION_NAME".equalsIgnoreCase(token.getContent())) {
            connectionName = true;
        } else if (token.isRegularIdentifier() && "CONSTRAINT_CATALOG".equalsIgnoreCase(token.getContent())) {
            constraintCatalog = true;
        } else if (token.isRegularIdentifier() && "CONSTRAINT_NAME".equalsIgnoreCase(token.getContent())) {
            constraintName = true;
        } else if (token.isRegularIdentifier() && "CONSTRAINT_SCHEMA".equalsIgnoreCase(token.getContent())) {
            constraintSchema = true;
        } else if (token.isRegularIdentifier() && "CURSOR_NAME".equalsIgnoreCase(token.getContent())) {
            cursorName = true;
        } else if (token.isRegularIdentifier() && "MESSAGE_LENGTH".equalsIgnoreCase(token.getContent())) {
            messageLength = true;
        } else if (token.isRegularIdentifier() && "MESSAGE_OCTET_LENGTH".equalsIgnoreCase(token.getContent())) {
            messageOctetLength = true;
        } else if (token.isRegularIdentifier() && "MESSAGE_TEXT".equalsIgnoreCase(token.getContent())) {
            messageText = true;
        } else if (token.isRegularIdentifier() && "PARAMETER_MODE".equalsIgnoreCase(token.getContent())) {
            parameterMode = true;
        } else if (token.isRegularIdentifier() && "PARAMETER_NAME".equalsIgnoreCase(token.getContent())) {
            parameterName = true;
        } else if (token.isRegularIdentifier() && "PARAMETER_ORDINAL_POSITION".equalsIgnoreCase(token.getContent())) {
            parameterOrdinalPosition = true;
        } else if (token.isRegularIdentifier() && "RETURNED_SQLSTATE".equalsIgnoreCase(token.getContent())) {
            returnedSqlstate = true;
        } else if (token.isRegularIdentifier() && "ROUTINE_CATALOG".equalsIgnoreCase(token.getContent())) {
            routineCatalog = true;
        } else if (token.isRegularIdentifier() && "ROUTINE_NAME".equalsIgnoreCase(token.getContent())) {
            routineName = true;
        } else if (token.isRegularIdentifier() && "ROUTINE_SCHEMA".equalsIgnoreCase(token.getContent())) {
            routineSchema = true;
        } else if (token.isRegularIdentifier() && "SCHEMA_NAME".equalsIgnoreCase(token.getContent())) {
            schemaName = true;
        } else if (token.isRegularIdentifier() && "SERVER_NAME".equalsIgnoreCase(token.getContent())) {
            serverName = true;
        } else if (token.isRegularIdentifier() && "SPECIFIC_NAME".equalsIgnoreCase(token.getContent())) {
            specificName = true;
        } else if (token.isRegularIdentifier() && "SUBCLASS_ORIGIN".equalsIgnoreCase(token.getContent())) {
            subclassOrigin = true;
        } else if (token.isRegularIdentifier() && "TABLE_NAME".equalsIgnoreCase(token.getContent())) {
            tableName = true;
        } else if (token.isRegularIdentifier() && "TRIGGER_CATALOG".equalsIgnoreCase(token.getContent())) {
            triggerCatalog = true;
        } else if (token.isRegularIdentifier() && "TRIGGER_NAME".equalsIgnoreCase(token.getContent())) {
            triggerName = true;
        } else if (token.isRegularIdentifier() && "TRIGGER_SCHEMA".equalsIgnoreCase(token.getContent())) {
            triggerSchema = true;
        } else {
            reset(markedIndex);
            return null;
        }
        return new ConditionInformationItemName(
            token.getBeginIndex(),
            token.getEndIndex(),
            catalogName,
            classOrigin,
            columnName,
            conditionNumber,
            connectionName,
            constraintCatalog,
            constraintName,
            constraintSchema,
            cursorName,
            messageLength,
            messageOctetLength,
            messageText,
            parameterMode,
            parameterName,
            parameterOrdinalPosition,
            returnedSqlstate,
            routineCatalog,
            routineName,
            routineSchema,
            schemaName,
            serverName,
            specificName,
            subclassOrigin,
            tableName,
            triggerCatalog,
            triggerName,
            triggerSchema
        );
    }

    private DescribeStatement tryParseDescribeStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDescribe()) {
            reset(markedIndex);
            return null;
        }
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isInput()) {
            SqlStatementName sqlStatementName = parseSqlStatementName();
            UsingDescriptor usingDescriptor = parseUsingDescriptor();
            NestingOption nestingOption = tryParseNestingOption();
            int endIndex;
            if (nestingOption != null) {
                endIndex = nestingOption.getEndIndex();
            } else {
                endIndex = usingDescriptor.getEndIndex();
            }
            return new DescribeInputStatement(
                    token.getBeginIndex(),
                    endIndex,
                    sqlStatementName,
                    usingDescriptor,
                    nestingOption);
        }
        boolean output;
        if (token1.isOutput()) {
            output = true;
        } else {
            reset(markedIndex1);
            output = false;
        }
        DescribedObject describedObject = parseDescribedObject();
        UsingDescriptor usingDescriptor = parseUsingDescriptor();
        NestingOption nestingOption = tryParseNestingOption();
        int endIndex;
        if (nestingOption == null) {
            endIndex = usingDescriptor.getEndIndex();
        } else {
            endIndex = nestingOption.getEndIndex();
        }
        return new DescribeOutputStatement(
                token.getBeginIndex(),
                endIndex,
                output,
                describedObject,
                usingDescriptor,
                nestingOption);
    }

    private DescribedObject parseDescribedObject() {
        DescribedObject describedObject = tryParseDescribedObject();
        if (describedObject != null) {
            return describedObject;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to described object.");
    }

    private DescribedObject tryParseDescribedObject() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isCursor()) {
            ExtendedCursorName extendedCursorName = parseExtendedCursorName();
            Token token1 = scan();
            if (!token1.isRegularIdentifier() || !"STRUCTURE".equalsIgnoreCase(token1.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a STRUCTURE.");
            }
            return new DescribedObject(token.getBeginIndex(), token1.getEndIndex(), null, extendedCursorName);
        }
        reset(markedIndex);
        SqlStatementName sqlStatementName = parseSqlStatementName();
        return new DescribedObject(token.getBeginIndex(), sqlStatementName.getEndIndex(), sqlStatementName, null);
    }

    private SqlStatementName parseSqlStatementName() {
        SqlStatementName sqlStatementName = tryParseSqlStatementName();
        if (sqlStatementName != null) {
            return sqlStatementName;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to SQL statement name.");
    }

    private SqlStatementName tryParseSqlStatementName() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() || token.isUnicodeDelimiterBody()) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isPeriod()) {
                reset(markedIndex1);
                return new Identifier(token);
            }
        }
        reset(markedIndex);
        ScopeOption scopeOption = tryParseScopeOption();
        SimpleValueSpecification simpleValueSpecification = parseSimpleValueSpecification();
        int beginIndex;
        if (scopeOption != null) {
            beginIndex = scopeOption.getBeginIndex();
        } else {
            beginIndex = simpleValueSpecification.getBeginIndex();
        }
        return new ExtendedStatementName(
                beginIndex,
                simpleValueSpecification.getEndIndex(),
                scopeOption,
                simpleValueSpecification);
    }

    private ExtendedStatementName parseExtendedStatementName() {
        ExtendedStatementName extendedStatementName = tryParseExtendedStatementName();
        if (extendedStatementName != null) {
            return extendedStatementName;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a extended statement name.");
    }

    // <extended statement name> ::= [ <scope option> ] <simple value specification>
    private ExtendedStatementName tryParseExtendedStatementName() {
        ScopeOption scopeOption = tryParseScopeOption();
        SimpleValueSpecification simpleValueSpecification = tryParseSimpleValueSpecification();
        if (simpleValueSpecification == null) {
            if (scopeOption == null) {
                return null;
            }
            Token token = scan();
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a simple value specification.");
        }
        int beginIndex;
        if (scopeOption != null) {
            beginIndex = scopeOption.getBeginIndex();
        } else {
            beginIndex = simpleValueSpecification.getBeginIndex();
        }
        return new ExtendedStatementName(
                beginIndex,
                simpleValueSpecification.getEndIndex(),
                scopeOption,
                simpleValueSpecification);
    }

    private UsingDescriptor parseUsingDescriptor() {
        UsingDescriptor usingDescriptor = tryParseUsingDescriptor();
        if (usingDescriptor != null) {
            return usingDescriptor;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to using descriptor.");
    }

    // USING [ SQL ] DESCRIPTOR <descriptor name>
    private UsingDescriptor tryParseUsingDescriptor() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isUsing()) {
            reset(markedIndex);
            return null;
        }
        boolean sql = false;
        Token token1 = scan();
        if (token1.isSql()) {
            sql = true;
            token1 = scan();
        }
        if (!token1.isRegularIdentifier() || !"DESCRIPTOR".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a DESCRIPTOR.");
        }
        DescriptorName descriptorName = parseDescriptorName();
        return new UsingDescriptor(token.getBeginIndex(), sql, descriptorName);
    }

    private NestingOption parseNestingOption() {
        NestingOption nestingOption = tryParseNestingOption();
        if (nestingOption != null) {
            return nestingOption;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to nesting option.");
    }

    private NestingOption tryParseNestingOption() {
        boolean withNesting;
        boolean withoutNesting;
        int markedIndex = mark();
        Token token = scan();
        if (token.isWith()) {
            withNesting = true;
            withoutNesting = false;
        } else if (token.isWithout()) {
            withNesting = false;
            withoutNesting = true;
        } else {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"NESTING".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a NESTING.");
        }
        return new NestingOption(token.getBeginIndex(), token1.getEndIndex(), withNesting, withoutNesting);
    }

    private MergeStatement parseMergeStatement() {
        MergeStatement mergeStatement = tryParseMergeStatement();
        if (mergeStatement != null) {
            return mergeStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to merge statement.");
    }

    private OpenStatement parseOpenStatement() {
        OpenStatement openStatement = tryParseOpenStatement();
        if (openStatement != null) {
            return openStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to open statement.");
    }

    // OPEN <cursor name>
    private OpenStatement tryParseOpenStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isOpen()) {
            reset(markedIndex);
            return null;
        }
        LocalQualifiedName cursorName = parseLocalQualifiedName();
        return new OpenStatement(token.getBeginIndex(), cursorName);
    }

    private PrepareStatement parsePrepareStatement() {
        PrepareStatement prepareStatement = tryParsePrepareStatement();
        if (prepareStatement != null) {
            return prepareStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to prepare statement.");
    }

    // PREPARE <SQL statement name> [ <attributes specification> ] FROM <SQL statement variable>
    private PrepareStatement tryParsePrepareStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isPrepare()) {
            reset(markedIndex);
            return null;
        }
        SqlStatementName sqlStatementName = parseSqlStatementName();
        SimpleValueSpecification attributesVariable = null;
        Token token1 = scan();
        if (token1.isRegularIdentifier() && "ATTRIBUTES".equalsIgnoreCase(token1.getContent())) {
            attributesVariable = parseSimpleValueSpecification();
            token1 = scan();
        }
        if (token1.isFrom()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a FROM.");
        }
        SimpleValueSpecification sqlStatementVariable = parseSimpleValueSpecification();
        return new PrepareStatement(token.getBeginIndex(), sqlStatementName, attributesVariable, sqlStatementVariable);
    }

    private RevokeStatement parseRevokeStatement() {
        RevokeStatement revokeStatement = tryParseRevokeStatement();
        if (revokeStatement != null) {
            return revokeStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to revoke statement.");
    }

    // <revoke privilege statement> | <revoke role statement>
    private RevokeStatement tryParseRevokeStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRevoke()) {
            reset(markedIndex);
            return null;
        }
        boolean grantOptionFor = false;
        boolean hierarchyOptionFor = false;
        boolean adminOptionFor = false;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isGrant()
                || token1.isRegularIdentifier() && (
                        "HIERARCHY".equalsIgnoreCase(token1.getContent())
                                || "ADMIN".equalsIgnoreCase(token1.getContent()))) {
            if (token1.isGrant()) {
                grantOptionFor = true;
            } else if ("HIERARCHY".equalsIgnoreCase(token1.getContent())) {
                hierarchyOptionFor = true;
            } else {
                adminOptionFor = true;
            }
            Token token2 = scan();
            if (!token2.isRegularIdentifier() || !"Option".equalsIgnoreCase(token2.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a OPTION.");
            }
            Token token3 = scan();
            if (!token3.isFor()) {
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a FOR.");
            }
        } else {
            reset(markedIndex1);
        }
        Privileges privileges = null;
        List<Identifier> roleRevokedList = null;
        if (grantOptionFor == true || hierarchyOptionFor == true) {
            privileges = parsePrivileges();
        } else if (adminOptionFor == true) {
            roleRevokedList = parseRoleRevokedList();
        } else {
            privileges = tryParsePrivileges();
            if (privileges == null) {
                roleRevokedList = tryParseRoleRevokedList();
                if (roleRevokedList == null) {
                    Token token2 = scan();
                    throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Cannot resolve to privileges or role revoked list.");
                }
            }
        }
        Token token2 = scan();
        if (!token2.isFrom()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a FROM.");
        }
        GranteeList granteeList = parseGranteeList();
        Grantor grantor = null;
        int markedIndex3 = mark();
        Token token3 = scan();
        if (token3.isRegularIdentifier() && "GRANTED".equalsIgnoreCase(token3.getContent())) {
            Token token4 = scan();
            if (!token4.isBy()) {
                throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a BY.");
            }
            grantor = parseGrantor();
        } else {
            reset(markedIndex3);
        }
        DropBehavior dropBehavior = parseDropBehavior();
        if (grantOptionFor == true || hierarchyOptionFor == true || privileges != null) {
            return new RevokePrivilegeStatement(
                    token.getBeginIndex(),
                    grantOptionFor,
                    hierarchyOptionFor,
                    privileges,
                    granteeList,
                    grantor,
                    dropBehavior
            );
        }
        return new RevokeRoleStatement(
                token.getBeginIndex(),
                adminOptionFor,
                roleRevokedList == null ? null : new IdentifierList(roleRevokedList),
                granteeList,
                grantor,
                dropBehavior
        );
    }

    private List<Identifier> parseRoleRevokedList() {
        List<Identifier> roleRevokedList = tryParseRoleRevokedList();
        if (roleRevokedList != null) {
            return roleRevokedList;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to role revoked list.");
    }

    private List<Identifier> tryParseRoleRevokedList() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() && !token.isUnicodeDelimiterBody()) {
            reset(markedIndex);
            return null;
        }
        Identifier roleRevoked = new Identifier(token);
        List<Identifier> roleRevokedList = new ArrayList<>();
        roleRevokedList.add(roleRevoked);
        while (true) {
            int markedIndex2 = mark();
            Token token2 = scan();
            if (!token2.isComma()) {
                reset(markedIndex2);
                break;
            }
            Identifier roleRevoked1 = parseIdentifier();
            roleRevokedList.add(roleRevoked1);
        }
        return roleRevokedList;
    }

    // ROLLBACK [ WORK ] [ AND [ NO ] CHAIN ] [ TO SAVEPOINT <savepoint specifier> ]
    private RollbackStatement tryParseRollbackStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRollback()) {
            reset(markedIndex);
            return null;
        }
        int endIndex = token.getEndIndex();
        boolean work = false;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isRegularIdentifier() && "".equalsIgnoreCase(token1.getContent())) {
            endIndex = token1.getEndIndex();
            work = true;
            markedIndex1 = mark();
            token1 = scan();
        }
        boolean andChain = false;
        boolean andNoChain = false;
        if (token1.isAnd()) {
            Token token2 = scan();
            if (token2.isNo()) {
                andNoChain = true;
                token2 = scan();
            } else {
                andChain = true;
            }
            if (!token2.isRegularIdentifier() || !"CHAIN".equalsIgnoreCase(token2.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a CHAIN.");
            }
            endIndex = token2.getEndIndex();
            markedIndex1 = mark();
            token1 = scan();
        }
        Identifier savepointSpecifier = null;
        if (token1.isTo()) {
            Token token2 = scan();
            if (!token2.isSavepoint()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a SAVEPOINT.");
            }
            Token token3 = scan();
            if (!token3.isRegularIdentifier() && !token3.isUnicodeDelimiterBody()) {
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a savepoint specifier.");
            }
            endIndex = token3.getEndIndex();
            savepointSpecifier = new Identifier(token3);
        } else {
            reset(markedIndex1);
        }
        return new RollbackStatement(token.getBeginIndex(), endIndex, work, andChain, andNoChain, savepointSpecifier);
    }

    private MergeStatement tryParseMergeStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isMerge()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isInto()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a INTO.");
        }
        TargetTable targetTable = parseTargetTable();
        Identifier mergeCorrelationName;
        int markedIndex2 = mark();
        Token token2 = scan();
        if (token2.isAs()) {
            mergeCorrelationName = parseIdentifier();
        } else {
            reset(markedIndex2);
            mergeCorrelationName = tryParseIdentifier();
        }
        Token token3 = scan();
        if (!token3.isUsing()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a USING.");
        }
        TableReference tableReference = parseTableReference();
        Token token4 = scan();
        if (!token4.isOn()) {
            throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a ON.");
        }
        BooleanValueExpression searchCondition = parseBooleanValueExpression();
        MergeWhenClause mergeOperationSpecification = parseMergeWhenClause();
        return new MergeStatement(
                token.getBeginIndex(),
                targetTable,
                mergeCorrelationName,
                tableReference,
                searchCondition,
                mergeOperationSpecification);
    }

    private MergeWhenClause parseMergeWhenClause() {
        MergeWhenClause mergeWhenClause = tryParseMergeWhenClause();
        if (mergeWhenClause != null) {
            return mergeWhenClause;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to merge when clause.");
    }

    private MergeWhenClause tryParseMergeWhenClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isWhen()) {
            reset(markedIndex);
            return null;
        }
        boolean not;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isNot()) {
            not = true;
        } else {
            reset(markedIndex1);
            not = false;
        }
        Token token2 = scan();
        if (!token2.isRegularIdentifier() || !"MATCHED".equalsIgnoreCase(token2.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a MATCHED.");
        }
        Token token3 = scan();
        if (!token3.isThen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a THEN.");
        }
        if (not) {
            MergeInsertSpecification mergeInsertSpecification = parseMergeInsertSpecification();
            return new MergeWhenNotMatchedClause(token.getBeginIndex(), mergeInsertSpecification);
        }
        MergeUpdateSpecification mergeUpdateSpecification = parseMergeUpdateSpecification();
        return new MergeWhenMatchedClause(token.getBeginIndex(), mergeUpdateSpecification);
    }

    private MergeInsertSpecification parseMergeInsertSpecification() {
        MergeInsertSpecification mergeInsertSpecification = tryParseMergeInsertSpecification();
        if (mergeInsertSpecification != null) {
            return mergeInsertSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to merge insert specification.");
    }

    private MergeInsertSpecification tryParseMergeInsertSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isInsert()) {
            reset(markedIndex);
            return null;
        }
        IdentifierList insertColumnList;
        Token token1 = scan();
        if (token1.isLeftParen()) {
            insertColumnList = parseIdentifierList();
            Token token2 = scan();
            if (!token2.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
            }
        } else {
            insertColumnList = null;
        }
        OverrideClause overrideClause = tryParseOverrideClause();
        Token token2 = scan();
        if (!token2.isValues()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a VALUES.");
        }
        List<MergeInsertValueElement> mergeInsertValueList = parseMergeInsertValueList();
        return new MergeInsertSpecification(
                token.getBeginIndex(),
                token2.getEndIndex(),
                insertColumnList,
                overrideClause,
                mergeInsertValueList);
    }

    private List<MergeInsertValueElement> parseMergeInsertValueList() {
        List<MergeInsertValueElement> mergeInsertValueList = tryParseMergeInsertValueList();
        if (mergeInsertValueList != null) {
            return mergeInsertValueList;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to merge insert value list.");
    }

    private List<MergeInsertValueElement> tryParseMergeInsertValueList() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        MergeInsertValueElement mergeInsertValueElement = parseMergeInsertValueElement();
        List<MergeInsertValueElement> mergeInsertValueList = new ArrayList<>();
        mergeInsertValueList.add(mergeInsertValueElement);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            MergeInsertValueElement mergeInsertValueElement1 = parseMergeInsertValueElement();
            mergeInsertValueList.add(mergeInsertValueElement1);
        }
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return mergeInsertValueList;
    }

    private MergeInsertValueElement parseMergeInsertValueElement() {
        MergeInsertValueElement mergeInsertValueElement = tryParseMergeInsertValueElement();
        if (mergeInsertValueElement != null) {
            return mergeInsertValueElement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to merge insert value element.");
    }

    private MergeInsertValueElement tryParseMergeInsertValueElement() {
        MergeInsertValueElement mergeInsertValueElement = tryParseValueExpression();
        if (mergeInsertValueElement != null) {
            return mergeInsertValueElement;
        }
        mergeInsertValueElement = tryParseContextuallyTypedValueSpecification();
        return mergeInsertValueElement;
    }

    private ContextuallyTypedValueSpecification tryParseContextuallyTypedValueSpecification() {
        ContextuallyTypedValueSpecification contextuallyTypedValueSpecification = tryParseImplicitlyTypedValueSpecification();
        if (contextuallyTypedValueSpecification != null) {
            return contextuallyTypedValueSpecification;
        }
        contextuallyTypedValueSpecification = tryParseDefaultSpecification();
        return contextuallyTypedValueSpecification;
    }

    private DefaultSpecification parseDefaultSpecification() {
        DefaultSpecification defaultSpecification = tryParseDefaultSpecification();
        if (defaultSpecification != null) {
            return defaultSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a DEFAULT.");
    }

    private DefaultSpecification tryParseDefaultSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDefault()) {
            reset(markedIndex);
            return null;
        }
        return new DefaultSpecification(token.getBeginIndex(), token.getEndIndex());
    }

    private OverrideClause parseOverrideClause() {
        OverrideClause overrideClause = tryParseOverrideClause();
        if (overrideClause != null) {
            return overrideClause;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a OVERRIDING.");
    }

    private OverrideClause tryParseOverrideClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"OVERRIDING".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        boolean overridingUserValue;
        boolean overridingSystemValue;
        Token token1 = scan();
        if (token1.isUser()) {
            overridingUserValue = true;
            overridingSystemValue = false;
        } else if (token1.isSystem()) {
            overridingUserValue = false;
            overridingSystemValue = true;
        } else {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a USER或SYSTEM.");
        }
        Token token2 = scan();
        if (!token2.isValue()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a VALUE.");
        }
        return new OverrideClause(
                token.getBeginIndex(),
                token2.getEndIndex(),
                overridingUserValue,
                overridingSystemValue);
    }

    private MergeUpdateSpecification parseMergeUpdateSpecification() {
        MergeUpdateSpecification mergeUpdateSpecification = tryParseMergeUpdateSpecification();
        if (mergeUpdateSpecification != null) {
            return mergeUpdateSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to merge update specification.");
    }

    private MergeUpdateSpecification tryParseMergeUpdateSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isUpdate()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isSet()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missint a SET.");
        }
        SetClauseList setClauseList = parseSetClauseList();
        return new MergeUpdateSpecification(token.getBeginIndex(), setClauseList);
    }

    private SetClauseList parseSetClauseList() {
        SetClause setClause = parseSetClause();
        List<SetClause> setClauseList = new ArrayList<>();
        setClauseList.add(setClause);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isComma()) {
                reset(markedIndex);
                break;
            }
            SetClause setClause1 = parseSetClause();
            setClauseList.add(setClause1);
        }
        return new SetClauseList(setClauseList);
    }

    private SetClause parseSetClause() {
        SetClause setClause = tryParseSetClause();
        if (setClause != null) {
            return setClause;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to set clause.");
    }

    private SetClause tryParseSetClause() {
        SetClause setClause = tryParseMultipleColumnAssignment();
        if (setClause != null) {
            return setClause;
        }
        return tryParseSingleColumnAssignment();
    }

    private SingleColumnAssignment tryParseSingleColumnAssignment() {
        SetTarget setTarget = tryParseSetTarget();
        if (setTarget == null) {
            return null;
        }
        Token token = scan();
        if (!token.isEqualsOperator()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a =.");
        }
        UpdateSource updateSource = parseUpdateSource();
        return new SingleColumnAssignment(setTarget, updateSource);
    }

    private UpdateSource parseUpdateSource() {
        UpdateSource updateSource = tryParseUpdateSource();
        if (updateSource != null) {
            return updateSource;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to update source.");
    }

    private UpdateSource tryParseUpdateSource() {
        UpdateSource updateSource = tryParseValueExpression();
        if (updateSource == null) {
            updateSource = tryParseContextuallyTypedValueSpecification();;
        }
        return updateSource;
    }

    private MultipleColumnAssignment tryParseMultipleColumnAssignment() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        SetTarget setTarget = parseSetTarget();
        List<SetTarget> setTargetList = new ArrayList<>();
        setTargetList.add(setTarget);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            SetTarget setTarget1 = parseSetTarget();
            setTargetList.add(setTarget1);
        }
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        Token token3 = scan();
        if (!token3.isEqualsOperator()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a =.");
        }
        ContextuallyTypedRowValueExpression assignedRow = parseContextuallyTypedRowValueExpression();
        return new MultipleColumnAssignment(token.getBeginIndex(), new SetTargetList(setTargetList), assignedRow);
    }

    private ContextuallyTypedRowValueExpression parseContextuallyTypedRowValueExpression() {
        ContextuallyTypedRowValueExpression contextuallyTypedRowValueExpression = tryParseContextuallyTypedRowValueExpression();
        if (contextuallyTypedRowValueExpression != null) {
            return contextuallyTypedRowValueExpression;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve contextually typed row value expression.");
    }

    private ContextuallyTypedRowValueExpression tryParseContextuallyTypedRowValueExpression() {
        ContextuallyTypedRowValueExpression contextuallyTypedRowValueExpression = tryParseNonparenthesizedValueExpressionPrimary();
        if (contextuallyTypedRowValueExpression == null) {
            contextuallyTypedRowValueExpression = tryParseContextuallyTypedRowValueConstructor();
        }
        return contextuallyTypedRowValueExpression;
    }

    private NonparenthesizedValueExpressionPrimary parseNonparenthesizedValueExpressionPrimary() {
        NonparenthesizedValueExpressionPrimary nonparenthesizedValueExpressionPrimary = tryParseNonparenthesizedValueExpressionPrimary();
        if (nonparenthesizedValueExpressionPrimary != null) {
            return nonparenthesizedValueExpressionPrimary;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to nonparenthesized value expression primary.");
    }

    //   <unsigned value specification>
    // | <column reference>
    // | <set function specification>
    // | <window function>
    // | <scalar subquery>
    // | <case expression>
    // | <cast specification>
    // | <field reference>
    // | <subtype treatment>
    // | <method invocation>
    // | <static method invocation>
    // | <new specification>
    // | <attribute or method reference>
    // | <reference resolution>
    // | <collection value constructor>
    // | <array element reference>
    // | <multiset element reference>
    // | <routine invocation>
    // | <next value expression>
    private NonparenthesizedValueExpressionPrimary tryParseNonparenthesizedValueExpressionPrimary() {
        UnsignedValueSpecification unsignedValueSpecification
                = tryParseUnsignedValueSpecification();
        if (unsignedValueSpecification != null) {
            return unsignedValueSpecification;
        }
        ColumnReference columnReference = tryParseColumnReference();
        if (columnReference != null) {
            return columnReference;
        }
        SetFunctionSpecification setFunctionSpecification = tryParseSetFunctionSpecification();
        if (setFunctionSpecification != null) {
            return setFunctionSpecification;
        }
        WindowFunction windowFunction = tryParseWindowFunction();
        if (windowFunction != null) {
            return windowFunction;
        }
        Subquery scalarSubquery = tryParseSubquery();
        if (scalarSubquery != null) {
            return scalarSubquery;
        }
        CaseExpression caseExpression = tryParseCaseExpression();
        if (caseExpression != null) {
            return caseExpression;
        }
        CastSpecification castSpecification = tryParseCastSpecification();
        if (castSpecification != null) {
            return castSpecification;
        }
        FieldReference fieldReference = tryParseFieldReference();
        if (fieldReference != null) {
            return fieldReference;
        }
        SubtypeTreatment subtypeTreatment = tryParseSubtypeTreatment();
        if (subtypeTreatment != null) {
            return subtypeTreatment;
        }
        MethodInvocation methodInvocation = tryParseMethodInvocation();
        if (methodInvocation != null) {
            return methodInvocation;
        }
        StaticMethodInvocation staticMethodInvocation = tryParseStaticMethodInvocation();
        if (staticMethodInvocation != null) {
            return staticMethodInvocation;
        }
        NewSpecification newSpecification = tryParseNewSpecification();
        if (newSpecification != null) {
            return newSpecification;
        }
        AttributeOrMethodReference attributeOrMethodReference = tryParseAttributeOrMethodReference();
        if (attributeOrMethodReference != null) {
            return attributeOrMethodReference;
        }
        ReferenceResolution referenceResolution = tryParseReferenceResolution();
        if (referenceResolution != null) {
            return referenceResolution;
        }
        CollectionValueConstructor collectionValueConstructor = tryParseCollectionValueConstructor();
        if (collectionValueConstructor != null) {
            return collectionValueConstructor;
        }
        ArrayElementReference arrayElementReference = tryParseArrayElementReference();
        if (arrayElementReference != null) {
            return arrayElementReference;
        }
        MultisetElementReference multisetElementReference = tryParseMultisetElementReference();
        if (multisetElementReference != null) {
            return multisetElementReference;
        }
        RoutineInvocation routineInvocation = tryParseRoutineInvocation();
        if (routineInvocation != null) {
            return routineInvocation;
        }
        NextValueExpression nextValueExpression = tryParseNextValueExpression();
        if (nextValueExpression != null) {
            return nextValueExpression;
        }
        return null;
    }

    // NEXT VALUE FOR <sequence generator name>
    private NextValueExpression tryParseNextValueExpression() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"NEXT".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isValue()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a VALUE.");
        }
        Token token2 = scan();
        if (!token2.isFor()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a FOR.");
        }
        SchemaQualifiedName sequenceGeneratorName = parseSchemaQualifiedName();
        return new NextValueExpression(token.getBeginIndex(), sequenceGeneratorName);
    }

    // ELEMENT <left paren> <multset value expression> <right paren>
    private MultisetElementReference tryParseMultisetElementReference() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isElement()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        MultisetValueExpression multisetValueExpression = parseMultisetValueExpression();
        Token token2 = scan();
        if (!token2.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new MultisetElementReference(
                token.getBeginIndex(),
                token2.getEndIndex(),
                multisetValueExpression
        );
    }

    // <array value expression> <left bracket or trigraph> <numeric value expression> <right bracket or trigraph>
    private ArrayElementReference tryParseArrayElementReference() {
        ArrayValueExpression arrayValueExpression = tryParseArrayValueExpression();
        if (arrayValueExpression == null) {
            return null;
        }
        Token token = scan();
        if (!token.isLeftBracket() && !token.isLeftBracketTrigraph()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a [ or ??(.");
        }
        NumericValueExpression numericValueExpression = parseNumericValueExpression();
        Token token1 = scan();
        if (!token1.isRightBracket() && !token1.isRightBracketTrigraph()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ] or ??).");
        }
        return new ArrayElementReference(
                token.getBeginIndex(),
                token1.getEndIndex(),
                numericValueExpression
        );
    }

    // <array value constructor> | <multiset value constructor>
    private CollectionValueConstructor tryParseCollectionValueConstructor() {
        ArrayValueConstructor arrayValueConstructor = tryParseArrayValueConstructor();
        if (arrayValueConstructor != null) {
            return arrayValueConstructor;
        }
        MultisetValueConstructor multisetValueConstructor = tryParseMultisetValueConstructor();
        if (multisetValueConstructor != null) {
            return multisetValueConstructor;
        }
        return null;
    }

    //   <array value constructor by enumeration>
    // | <array value constructor by query>
    private ArrayValueConstructor tryParseArrayValueConstructor() {
        ArrayValueConstructorByQuery arrayValueConstructorByQuery
                = tryParseArrayValueConstructorByQuery();
        if (arrayValueConstructorByQuery != null) {
            return arrayValueConstructorByQuery;
        }
        ArrayValueConstructorByEnumeration arrayValueConstructorByEnumeration
                = tryParseArrayValueConstructorByEnumeration();
        if (arrayValueConstructorByEnumeration != null) {
            return arrayValueConstructorByEnumeration;
        }
        return null;
    }

    // ARRAY <left bracket or trigraph> <array element list> <right bracket or trigraph>
    private ArrayValueConstructorByEnumeration tryParseArrayValueConstructorByEnumeration() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isArray()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftBracket() && !token1.isLeftBracketTrigraph()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a [ or ??(.");
        }
        List<ValueExpression> arrayElementList = parseValueExpressionList();
        Token token2 = scan();
        if (!token2.isRightBracket() && !token2.isRightBracketTrigraph()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ] or ??).");
        }
        return new ArrayValueConstructorByEnumeration(
                token.getBeginIndex(),
                token2.getEndIndex(),
                new ArrayElementList(arrayElementList)
        );
    }

    private ArrayElementList parseArrayElementList() {
        List<ValueExpression> arrayElementList = parseValueExpressionList();
        return new ArrayElementList(arrayElementList);
    }

    // ARRAY <left paren> <query expression> [ <order by clause> ] <right paren>
    private ArrayValueConstructorByQuery tryParseArrayValueConstructorByQuery() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isArray()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        int markedIndex2 = mark();
        Token token2 = scan();
        if (!token2.isSelect() && !token2.isValues() && !token2.isTable()) {
            reset(markedIndex);
            return null;
        }
        reset(markedIndex2);
        QueryExpression queryExpression = parseQueryExpression();
        OrderByClause orderByClause = tryParseOrderByClause();
        Token token3 = scan();
        if (!token3.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
        }
        return new ArrayValueConstructorByQuery(
                token.getBeginIndex(),
                token3.getEndIndex(),
                queryExpression,
                orderByClause
        );
    }

    //   <multiset value constructor by enumeration>
    // | <multiset value constructor by query>
    // | <table value constructor by query>
    private MultisetValueConstructor tryParseMultisetValueConstructor() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isMultiset()) {
            Token token1 = scan();
            if (token1.isLeftBracket() || token1.isLeftBracketTrigraph()) {
                List<ValueExpression> multisetElementList = parseValueExpressionList();
                Token token2 = scan();
                if (!token2.isLeftBracket() && !token2.isLeftBracketTrigraph()) {
                    throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ] or ??).");
                }
                return new MultisetValueConstructorByEnumeration(
                        token.getBeginIndex(),
                        token2.getEndIndex(),
                        multisetElementList
                );
            } else if (token1.isLeftParen()) {
                QueryExpression queryExpression = parseQueryExpression();
                Token token2 = scan();
                if (!token2.isLeftParen()) {
                    throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
                }
                return new MultisetValueConstructorByQuery(
                        token.getBeginIndex(),
                        token2.getEndIndex(),
                        queryExpression
                );
            } else {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a [, ??( or (.");
            }
        } else if (token.isTable()) {
            Token token1 = scan();
            if (!token1.isLeftParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
            }
            QueryExpression queryExpression = parseQueryExpression();
            Token token2 = scan();
            if (!token2.isLeftParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
            }
            return new TableValueConstructorByQuery(
                    token.getBeginIndex(),
                    token2.getEndIndex(),
                    queryExpression
            );
        } else {
            reset(markedIndex);
            return null;
        }
    }

    // DEREF <left paren> <reference value expression> <right paren>
    private ReferenceResolution tryParseReferenceResolution() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDeref()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        ValueExpressionPrimary referenceValueExpression = parseValueExpressionPrimary();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new ReferenceResolution(token.getBeginIndex(), token2.getEndIndex(), referenceValueExpression);
    }

    // <value expression primary>
    // <dereference operator>
    // <qualified identifier>
    // [ <SQL argument list> ]
    private AttributeOrMethodReference tryParseAttributeOrMethodReference() {
        ValueExpressionPrimary valueExpressionPrimary = tryParseValueExpressionPrimary();
        if (valueExpressionPrimary == null) {
            return null;
        }
        RightArrow dereferenceOperator = tryParseRightArrow();
        Identifier qualifiedIdentifier = parseIdentifier();
        SqlArgumentList sqlArgumentList = tryParseSqlArgumentList();
        return new AttributeOrMethodReference(
                valueExpressionPrimary,
                dereferenceOperator,
                qualifiedIdentifier,
                sqlArgumentList
        );
    }

    private RightArrow parseRightArrow() {
        RightArrow rightArrow = tryParseRightArrow();
        if (rightArrow != null) {
            return rightArrow;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to right arrow.");
    }

    // <minus sign> <greater than operator>
    private RightArrow tryParseRightArrow() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isMinusSign()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isGreaterThanOperator()) {
            reset(markedIndex);
            return null;
        }
        return new RightArrow(token.getBeginIndex(), token1.getEndIndex());
    }

    // NEW <routine invocation>
    private NewSpecification tryParseNewSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isNew()) {
            reset(markedIndex);
            return null;
        }
        RoutineInvocation routineInvocation = parseRoutineInvocation();
        return new NewSpecification(token.getBeginIndex(), routineInvocation);
    }

    // <path-resolved user-defined type name> <double colon> <method name> [ <SQL argument list> ]
    private StaticMethodInvocation tryParseStaticMethodInvocation() {
        int markedIndex = mark();
        SchemaQualifiedName pathResolvedUserDefinedTypeName = tryParseSchemaQualifiedName();
        if (pathResolvedUserDefinedTypeName == null) {
            return null;
        }
        Token token = scan();
        if (!token.isColon()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isColon()) {
            reset(markedIndex);
            return null;
        }
        Identifier methodName = parseIdentifier();
        SqlArgumentList sqlArgumentList = tryParseSqlArgumentList();
        return new StaticMethodInvocation(
                pathResolvedUserDefinedTypeName,
                methodName,
                sqlArgumentList
        );
    }

    //   <value expression primary> <period> <method name> [ <SQL argument list> ]
    // | <left paren> <value expression primary> AS <data type> <right paren> <period> <method name> [ <SQL argument list> ]
    private MethodInvocation tryParseMethodInvocation() {
        ValueExpressionPrimary valueExpressionPrimary;
        DataType dataType = null;
        int markedIndex = mark();
        Token token = scan();
        if (token.isLeftParen()) {
            valueExpressionPrimary = tryParseValueExpressionPrimary();
            if (valueExpressionPrimary == null) {
                reset(markedIndex);
                return null;
            }
            Token token1 = scan();
            if (!token1.isAs()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a AS.");
            }
            dataType = parseDataType();
            Token token2 = scan();
            if (!token2.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
            }
        } else {
            reset(markedIndex);
            valueExpressionPrimary = tryParseValueExpressionPrimary();
            if (valueExpressionPrimary == null) {
                reset(markedIndex);
                return null;
            }
        }
        Token token1 = scan();
        if (!token1.isPeriod()) {
            reset(markedIndex);
            return null;
        }
        Identifier methodName = parseIdentifier();
        SqlArgumentList sqlArgumentList = tryParseSqlArgumentList();
        return new MethodInvocation(
                token.getBeginIndex(),
                valueExpressionPrimary,
                dataType,
                methodName,
                sqlArgumentList
        );
    }

    // TREAT <left paren> <subtype operand> AS <target subtype> <right paren>
    private SubtypeTreatment tryParseSubtypeTreatment() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isTreat()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        ValueExpression subtypeOperand = parseValueExpression();
        Token token2 = scan();
        if (!token2.isAs()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a AS.");
        }
        TargetSubtype targetSubtype = parseTargetSubtype();
        Token token3 = scan();
        if (!token3.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
        }
        return new SubtypeTreatment(
                token.getBeginIndex(),
                token3.getEndIndex(),
                subtypeOperand,
                targetSubtype
        );
    }

    private TargetSubtype parseTargetSubtype() {
        SchemaQualifiedName pathResolvedUserDefinedTypeName = tryParseSchemaQualifiedName();
        if (pathResolvedUserDefinedTypeName != null) {
            return pathResolvedUserDefinedTypeName;
        }
        ReferenceType referenceType = tryParseReferenceType();
        if (referenceType != null) {
            return referenceType;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to target subtype.");
    }

    private FieldReference tryParseFieldReference() {
        int markedIndex = mark();
        ValueExpressionPrimary valueExpressionPrimary = tryParseValueExpressionPrimary();
        if (valueExpressionPrimary == null) {
            return null;
        }
        Token token = scan();
        if (!token.isPeriod()) {
            reset(markedIndex);
        }
        Identifier fieldName = parseIdentifier();
        return new FieldReference(valueExpressionPrimary, fieldName);
    }

    // CAST <left paren> <cast operand> AS <cast target> <right paren>
    private CastSpecification tryParseCastSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCast()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        CastOperand castOperand = parseCastOperand();
        Token token2 = scan();
        if (!token2.isAs()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a AS.");
        }
        CastTarget castTarget = parseCastTarget();
        Token token3 = scan();
        if (!token3.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
        }
        return new CastSpecification(
                token.getBeginIndex(),
                token3.getEndIndex(),
                castOperand,
                castTarget
        );
    }

    // <value expression> | <implicitly typed value specification>
    private CastOperand parseCastOperand() {
        ValueExpression valueExpression = tryParseValueExpression();
        if (valueExpression != null) {
            return valueExpression;
        }
        ImplicitlyTypedValueSpecification implicitlyTypedValueSpecification
                = tryParseImplicitlyTypedValueSpecification();
        if (implicitlyTypedValueSpecification != null) {
            return implicitlyTypedValueSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to cast operand.");
    }

    // <domain name> | <data type>
    private CastTarget parseCastTarget() {
        SchemaQualifiedName domainName = tryParseSchemaQualifiedName();
        if (domainName != null) {
            return domainName;
        }
        DataType dataType = tryParseDataType();
        if (dataType != null) {
            return dataType;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to cast target.");
    }

    // <case abbreviation> | <case specification>
    private CaseExpression tryParseCaseExpression() {
        CaseAbbreviation caseAbbreviation = tryParseCaseAbbreviation();
        if (caseAbbreviation != null) {
            return caseAbbreviation;
        }
        CaseSpecification caseSpecification = tryParseCaseSpecification();
        if (caseSpecification != null) {
            return caseSpecification;
        }
        return null;
    }

    //   NULLIF <left paren> <value expression> <comma> <value expression> <right paren>
    // | COALESCE <left paren> <value expression> { <comma> <value expression> }... <right paren>
    private CaseAbbreviation tryParseCaseAbbreviation() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier()) {
            if ("NULLIF".equalsIgnoreCase(token.getContent())) {
                Token token1 = scan();
                if (!token1.isLeftParen()) {
                    throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
                }
                ValueExpression valueExpression = parseValueExpression();
                List<ValueExpression> valueExpressionList = new ArrayList<>();
                valueExpressionList.add(valueExpression);
                Token token2 = scan();
                if (!token2.isComma()) {
                    throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ,.");
                }
                ValueExpression valueExpression1 = parseValueExpression();
                valueExpressionList.add(valueExpression1);
                Token token3 = scan();
                if (!token3.isRightParen()) {
                    throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
                }
                return new CaseAbbreviation(
                        token.getBeginIndex(),
                        token3.getEndIndex(),
                        true,
                        false,
                        valueExpressionList
                );
            }
            if ("COALESCE".equalsIgnoreCase(token.getContent())) {
                Token token1 = scan();
                if (!token1.isLeftParen()) {
                    throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
                }
                ValueExpression valueExpression = parseValueExpression();
                List<ValueExpression> valueExpressionList = new ArrayList<>();
                valueExpressionList.add(valueExpression);
                while (true) {
                    int markedIndex2 = mark();
                    Token token2 = scan();
                    if (!token2.isComma()) {
                        reset(markedIndex2);
                        break;
                    }
                    ValueExpression valueExpression1 = parseValueExpression();
                    valueExpressionList.add(valueExpression1);
                }
                Token token2 = scan();
                if (!token2.isRightParen()) {
                    throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
                }
                return new CaseAbbreviation(
                        token.getBeginIndex(),
                        token2.getEndIndex(),
                        false,
                        true,
                        valueExpressionList
                );
            }
        }
        reset(markedIndex);
        return null;
    }

    // <simple case> | <searched case>
    private CaseSpecification tryParseCaseSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCase()) {
            reset(markedIndex);
            return null;
        }
        int markedIndex1 = mark();
        Token token1 = scan();
        reset(markedIndex1);
        if (token1.isWhen()) {
            SearchedWhenClause searchedWhenClause = parseSearchedWhenClause();
            List<SearchedWhenClause> searchedWhenClauseList = new ArrayList<>();
            searchedWhenClauseList.add(searchedWhenClause);
            while (true) {
                SearchedWhenClause searchedWhenClause1 = tryParseSearchedWhenClause();
                if (searchedWhenClause1 == null) {
                    break;
                }
                searchedWhenClauseList.add(searchedWhenClause1);
            }
            ElseClause elseClause = tryParseElseClause();
            Token token2 = scan();
            if (!token2.isEnd()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a END.");
            }
            return new SearchedCase(
                    token.getBeginIndex(),
                    token2.getEndIndex(),
                    searchedWhenClauseList,
                    elseClause
            );
        }
        CaseOperand caseOperand = parseCaseOperand();
        SimpleWhenClause simpleWhenClause = parseSimpleWhenClause();
        List<SimpleWhenClause> simpleWhenClauseList = new ArrayList<>();
        simpleWhenClauseList.add(simpleWhenClause);
        while (true) {
            SimpleWhenClause simpleWhenClause1 = tryParseSimpleWhenClause();
            if (simpleWhenClause1 == null) {
                break;
            }
            simpleWhenClauseList.add(simpleWhenClause1);
        }
        ElseClause elseClause = tryParseElseClause();
        Token token2 = scan();
        if (!token2.isEnd()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a END.");
        }
        return new SimpleCase(
                token.getBeginIndex(),
                token2.getEndIndex(),
                caseOperand,
                simpleWhenClauseList,
                elseClause
        );
    }

    private SearchedWhenClause parseSearchedWhenClause() {
        SearchedWhenClause searchedWhenClause = tryParseSearchedWhenClause();
        if (searchedWhenClause != null) {
            return searchedWhenClause;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to searched when clause.");
    }

    private SearchedWhenClause tryParseSearchedWhenClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isWhen()) {
            reset(markedIndex);
            return null;
        }
        BooleanValueExpression searchCondition = parseBooleanValueExpression();
        Token token1 = scan();
        if (!token1.isThen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a THEN.");
        }
        Result result = parseResult();
        return new SearchedWhenClause(token.getBeginIndex(), searchCondition, result);
    }

    private ElseClause tryParseElseClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isElse()) {
            reset(markedIndex);
            return null;
        }
        Result result = parseResult();
        return new ElseClause(token.getBeginIndex(), result);
    }

    private Result parseResult() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isNull()) {
            return new Result(token.getBeginIndex(), token.getEndIndex());
        }
        reset(markedIndex);
        ValueExpression resultExpression = parseValueExpression();
        return new Result(resultExpression);
    }

    // <row value predicand> | <overlaps predicate part>
    private CaseOperand parseCaseOperand() {
        RowValuePredicand rowValuePredicand = tryParseRowValuePredicand();
        if (rowValuePredicand == null) {
            return null;
        }
        int markedIndex = mark();
        Token token = scan();
        if (!token.isOverlaps()) {
            reset(markedIndex);
            return rowValuePredicand;
        }
        RowValuePredicand rowValuePredicand2 = parseRowValuePredicand();
        return new OverlapsPredicatePart(rowValuePredicand, rowValuePredicand2);
    }

    private SimpleWhenClause parseSimpleWhenClause() {
        SimpleWhenClause simpleWhenClause = tryParseSimpleWhenClause();
        if (simpleWhenClause != null) {
            return simpleWhenClause;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to simple when clause.");
    }

    private SimpleWhenClause tryParseSimpleWhenClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isWhen()) {
            reset(markedIndex);
            return null;
        }
        WhenOperand whenOperand = parseWhenOperand();
        Token token1 = scan();
        if (!token1.isThen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a THEN.");
        }
        Result result = parseResult();
        return new SimpleWhenClause(token.getBeginIndex(), whenOperand, result);
    }

    //   <row value predicand>
    // | <comparison predicate part 2>
    // | <between predicate part 2>
    // | <in predicate part 2>
    // | <character like predicate part 2>
    // | <octet like predicate part 2>
    // | <similar predicate part 2>
    // | <null predicate part 2>
    // | <quantified comparison predicate part 2>
    // | <match predicate part 2>
    // | <overlaps predicate part 2>
    // | <distinct predicate part 2>
    // | <member predicate part 2>
    // | <submultiset predicate part 2>
    // | <set predicate part 2>
    // | <type predicate part 2>
    private WhenOperand parseWhenOperand() {
        RowValuePredicand rowValuePredicand = tryParseRowValuePredicand();
        if (rowValuePredicand != null) {
            return rowValuePredicand;
        }
        ComparisonPredicatePart2 comparisonPredicatePart2 = tryParseComparisonPredicatePart2();
        if (comparisonPredicatePart2 != null) {
            return comparisonPredicatePart2;
        }
        BetweenPredicatePart2 betweenPredicatePart2 = tryParseBetweenPredicatePart2();
        if (betweenPredicatePart2 != null) {
            return betweenPredicatePart2;
        }
        InPredicatePart2 inPredicatePart2 = tryParseInPredicatePart2();
        if (inPredicatePart2 != null) {
            return inPredicatePart2;
        }
        LikePredicatePart2 likePredicatePart2 = tryParseLikePredicatePart2();
        if (likePredicatePart2 != null) {
            return likePredicatePart2;
        }
        SimilarPredicatePart2 similarPredicatePart2 = tryParseSimilarPredicatePart2();
        if (similarPredicatePart2 != null) {
            return similarPredicatePart2;
        }
        NullPredicatePart2 nullPredicatePart2 = tryParseNullPredicatePart2();
        if (nullPredicatePart2 != null) {
            return nullPredicatePart2;
        }
        QuantifiedComparisonPredicatePart2 quantifiedComparisonPredicatePart2
                = tryParseQuantifiedComparisonPredicatePart2();
        if (quantifiedComparisonPredicatePart2 != null) {
            return quantifiedComparisonPredicatePart2;
        }
        MatchPredicatePart2 matchPredicatePart2 = tryParseMatchPredicatePart2();
        if (matchPredicatePart2 != null) {
            return matchPredicatePart2;
        }
        OverlapsPredicatePart2 overlapsPredicatePart2 = tryParseOverlapsPredicatePart2();
        if (overlapsPredicatePart2 != null) {
            return overlapsPredicatePart2;
        }
        DistinctPredicatePart2 distinctPredicatePart2 = tryParseDistinctPredicatePart2();
        if (distinctPredicatePart2 != null) {
            return distinctPredicatePart2;
        }
        MemberPredicatePart2 memberPredicatePart2 = tryParseMemberPredicatePart2();
        if (memberPredicatePart2 != null) {
            return memberPredicatePart2;
        }
        SubmultisetPredicatePart2 submultisetPredicatePart2 = tryParseSubmultisetPredicatePart2();
        if (submultisetPredicatePart2 != null) {
            return submultisetPredicatePart2;
        }
        SetPredicatePart2 setPredicatePart2 = tryParseSetPredicatePart2();
        if (setPredicatePart2 != null) {
            return setPredicatePart2;
        }
        TypePredicatePart2 typePredicatePart2 = tryParseTypePredicatePart2();
        if (typePredicatePart2 != null) {
            return typePredicatePart2;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to when operand.");
    }

    private UnsignedValueSpecification parseUnsignedValueSpecification() {
        UnsignedValueSpecification unsignedValueSpecification = tryParseUnsignedValueSpecification();
        if (unsignedValueSpecification != null) {
            return unsignedValueSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to unsigned value specification.");
    }

    private UnsignedValueSpecification tryParseUnsignedValueSpecification() {
        UnsignedLiteral unsignedLiteral = tryParseUnsignedLiteral();
        if (unsignedLiteral != null) {
            return unsignedLiteral;
        }
        return tryParseGeneralValueSpecification();
    }

    private UnsignedLiteral tryParseUnsignedLiteral() {
        UnsignedNumericLiteral unsignedNumericLiteral = tryParseUnsignedNumericLiteral();
        if (unsignedNumericLiteral != null) {
            return unsignedNumericLiteral;
        }
        GeneralLiteral generalLiteral = tryParseGeneralLiteral();
        if (generalLiteral != null) {
            return generalLiteral;
        }
        return null;
    }

    private SimpleTable tryParseSimpleTable() {
        QuerySpecification querySpecification = tryParseQuerySpecification();
        if (querySpecification != null) {
            return querySpecification;
        }
        TableValueConstructor tableValueConstructor = tryParseTableValueConstructor();
        if (tableValueConstructor != null) {
            return tableValueConstructor;
        }
        ExplicitTable explicitTable = tryParseExplicitTable();
        return explicitTable;
    }

    private QuerySpecification tryParseQuerySpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSelect()) {
            reset(markedIndex);
            return null;
        }
        SetQuantifier setQuantifier = tryParseSetQuantifier();
        SelectList selectList = parseSelectList();
        TableExpression tableExpression = parseTableExpression();
        return new QuerySpecification(token.getBeginIndex(), setQuantifier, selectList, tableExpression);
    }

    // VALUES <row value expression list>
    private TableValueConstructor tryParseTableValueConstructor() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isValues()) {
            reset(markedIndex);
            return null;
        }
        TableRowValueExpression tableRowValueExpression = parseTableRowValueExpression();
        List<TableRowValueExpression> rowValueExpressionList = new ArrayList<>();
        rowValueExpressionList.add(tableRowValueExpression);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            TableRowValueExpression tableRowValueExpression1 = parseTableRowValueExpression();
            rowValueExpressionList.add(tableRowValueExpression1);
        }
        return new TableValueConstructor(token.getBeginIndex(), rowValueExpressionList);
    }

    // <row value special case> | <row value constructor>
    private TableRowValueExpression parseTableRowValueExpression() {
        NonparenthesizedValueExpressionPrimary rowValueSpecialCase
                = tryParseNonparenthesizedValueExpressionPrimary();
        if (rowValueSpecialCase != null) {
            return rowValueSpecialCase;
        }
        RowValueConstructor rowValueConstructor = tryParseRowValueConstructor();
        if (rowValueConstructor != null) {
            return rowValueConstructor;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to table row value expression.");
    }

    //   <common value expression>
    // | <boolean value expression>
    // | <explicit row value constructor>
    private RowValueConstructor tryParseRowValueConstructor() {
        CommonValueExpression commonValueExpression = tryParseCommonValueExpression();
        if (commonValueExpression != null) {
            return commonValueExpression;
        }
        BooleanValueExpression booleanValueExpression = tryParseBooleanValueExpression();
        if (booleanValueExpression != null) {
            return booleanValueExpression;
        }
        ExplicitRowValueConstructor explicitRowValueConstructor = tryParseExplicitRowValueConstructor();
        if (explicitRowValueConstructor != null) {
            return explicitRowValueConstructor;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to row value constructor.");
    }

    // TABLE <table or query name>
    private ExplicitTable tryParseExplicitTable() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isTable()) {
            reset(markedIndex);
            return null;
        }
        LocalOrSchemaQualifiedName tableOrQueryName = parseLocalOrSchemaQualifiedName();
        return new ExplicitTable(token.getBeginIndex(), tableOrQueryName);
    }

    private ColumnReference parseColumnReference() {
        ColumnReference columnReference = tryParseColumnReference();
        if (columnReference != null) {
            return columnReference;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to column reference.");
    }

    private ColumnReference tryParseColumnReference() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() || token.isUnicodeDelimiterBody()) {
            reset(markedIndex);
            return tryParseIdentifierChain();
        }
        if (token.isModule()) {
            Token token1 = scan();
            if (!token1.isPeriod()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ..");
            }
            Identifier qualifiedIdentifier = parseIdentifier();
            Token token2 = scan();
            if (!token2.isPeriod()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ..");
            }
            Identifier columnName = parseIdentifier();
            return new ModuleQualifiedIdentifierColumnName(token.getBeginIndex(), qualifiedIdentifier, columnName);
        }
        reset(markedIndex);
        return null;
    }

    private SetFunctionSpecification parseSetFunctionSpecification() {
        SetFunctionSpecification setFunctionSpecification = tryParseSetFunctionSpecification();
        if (setFunctionSpecification != null) {
            return setFunctionSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to set function specification.");
    }

    private SetFunctionSpecification tryParseSetFunctionSpecification() {
        SetFunctionSpecification setFunctionSpecification = tryParseAggregateFunction();
        if (setFunctionSpecification == null) {
            setFunctionSpecification = tryParseGroupingOperation();
        }
        return setFunctionSpecification;
    }

    private AggregateFunction parseAggregateFunction() {
        AggregateFunction aggregateFunction = tryParseAggregateFunction();
        if (aggregateFunction != null) {
            return aggregateFunction;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to aggregate function.");
    }

    private AggregateFunction tryParseAggregateFunction() {
        CountAsteriskAggregateFunction countAsteriskAggregateFunction = tryParseCountAsteriskAggregateFunction();
        if (countAsteriskAggregateFunction != null) {
            return countAsteriskAggregateFunction;
        }
        GeneralSetFunctionAggregateFunction generalSetFunctionAggregateFunction
                = tryParseGeneralSetFunctionAggregateFunction();
        if (generalSetFunctionAggregateFunction != null) {
            return generalSetFunctionAggregateFunction;
        }
        BinarySetFunctionAggregateFunction binarySetFunctionAggregateFunction
                = tryParseBinarySetFunctionAggregateFunction();
        if (binarySetFunctionAggregateFunction != null) {
            return binarySetFunctionAggregateFunction;
        }
        OrderedSetFunctionAggregateFunction orderedSetFunctionAggregateFunction
                = tryParseOrderedSetFunctionAggregateFunction();
        return orderedSetFunctionAggregateFunction;
    }

    // COUNT <left paren> <asterisk> <right paren> [ <filter clause> ]
    private CountAsteriskAggregateFunction tryParseCountAsteriskAggregateFunction() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"COUNT".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isAsterisk()) {
            reset(markedIndex);
            return null;
        }
        Token token3 = scan();
        if (!token3.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
        }
        FilterClause filterClause = tryParseFilterClause();
        int endIndex;
        if (filterClause != null) {
            endIndex = filterClause.getEndIndex();
        } else {
            endIndex = token3.getEndIndex();
        }
        return new CountAsteriskAggregateFunction(token.getBeginIndex(), endIndex, filterClause);
    }

    // <general set function> [ <filter clause> ]
    private GeneralSetFunctionAggregateFunction tryParseGeneralSetFunctionAggregateFunction() {
        GeneralSetFunction generalSetFunction = tryParseGeneralSetFunction();
        if (generalSetFunction == null) {
            return null;
        }
        FilterClause filterClause = tryParseFilterClause();
        int endIndex;
        if (filterClause != null) {
            endIndex = filterClause.getEndIndex();
        } else {
            endIndex = generalSetFunction.getEndIndex();
        }
        return new GeneralSetFunctionAggregateFunction(
                generalSetFunction.getBeginIndex(),
                endIndex,
                generalSetFunction,
                filterClause
        );
    }

    // <binary set function> [ <filter clause> ]
    private BinarySetFunctionAggregateFunction tryParseBinarySetFunctionAggregateFunction() {
        BinarySetFunction binarySetFunction = tryParseBinarySetFunction();
        if (binarySetFunction == null) {
            return null;
        }
        FilterClause filterClause = tryParseFilterClause();
        int endIndex;
        if (filterClause != null) {
            endIndex = filterClause.getEndIndex();
        } else {
            endIndex = binarySetFunction.getEndIndex();
        }
        return new BinarySetFunctionAggregateFunction(
                binarySetFunction.getBeginIndex(),
                endIndex,
                binarySetFunction,
                filterClause
        );
    }

    private OrderedSetFunctionAggregateFunction tryParseOrderedSetFunctionAggregateFunction() {
        OrderedSetFunction orderedSetFunction = tryParseOrderedSetFunction();
        if (orderedSetFunction == null) {
            return null;
        }
        FilterClause filterClause = tryParseFilterClause();
        int endIndex;
        if (filterClause != null) {
            endIndex = filterClause.getEndIndex();
        } else {
            endIndex = orderedSetFunction.getEndIndex();
        }
        return new OrderedSetFunctionAggregateFunction(
                orderedSetFunction.getBeginIndex(),
                endIndex,
                orderedSetFunction,
                filterClause
        );
    }

    private OrderedSetFunction tryParseOrderedSetFunction() {
        HypotheticalSetFunction hypotheticalSetFunction = tryParseHypotheticalSetFunction();
        if (hypotheticalSetFunction != null) {
            return hypotheticalSetFunction;
        }
        InverseDistributionFunction inverseDistributionFunction = tryParseInverseDistributionFunction();
        return inverseDistributionFunction;
    }

    // <rank function type> <left paren> <hypothetical set function value expression list> <right paren> <within group specification>
    private HypotheticalSetFunction tryParseHypotheticalSetFunction() {
        RankFunctionType rankFunctionType = tryParseRankFunctionType();
        if (rankFunctionType == null) {
            return null;
        }
        Token token = scan();
        if (!token.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a (.");
        }
        ValueExpression hypotheticalSetFunctionValueExpression = parseValueExpression();
        List<ValueExpression> hypotheticalSetFunctionValueExpressionList = new ArrayList<>();
        hypotheticalSetFunctionValueExpressionList.add(hypotheticalSetFunctionValueExpression);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            ValueExpression hypotheticalSetFunctionValueExpression1 = parseValueExpression();
            hypotheticalSetFunctionValueExpressionList.add(hypotheticalSetFunctionValueExpression1);
        }
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        WithinGroupSpecification withinGroupSpecification = parseWithinGroupSpecification();
        return new HypotheticalSetFunction(
                rankFunctionType.getBeginIndex(),
                withinGroupSpecification.getEndIndex(),
                rankFunctionType,
                hypotheticalSetFunctionValueExpressionList,
                withinGroupSpecification
        );
    }

    private WithinGroupSpecification parseWithinGroupSpecification() {
        WithinGroupSpecification withinGroupSpecification = tryParseWithinGroupSpecification();
        if (withinGroupSpecification != null) {
            return withinGroupSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to within group specification.");
    }

    // WITHIN GROUP <left paren> ORDER BY <sort specification list> <right paren>
    private WithinGroupSpecification tryParseWithinGroupSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isWithin()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isGroup()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a GROUP.");
        }
        Token token2 = scan();
        if (!token2.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a (.");
        }
        Token token3 = scan();
        if (!token3.isOrder()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ORDER.");
        }
        Token token4 = scan();
        if (!token4.isBy()) {
            throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a BY.");
        }
        SortSpecificationList sortSpecificationList = parseSortSpecificationList();
        Token token5 = scan();
        if (!token5.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing a ).");
        }
        return new WithinGroupSpecification(
                token.getBeginIndex(),
                token5.getEndIndex(),
                sortSpecificationList
        );
    }

    // <inverse distribution function type> <left paren> <inverse distribution function argument> <right paren> <within group specification>
    private InverseDistributionFunction tryParseInverseDistributionFunction() {
        InverseDistributionFunctionType inverseDistributionFunctionType = tryParseInverseDistributionFunctionType();
        if (inverseDistributionFunctionType == null) {
            return null;
        }
        Token token = scan();
        if (!token.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a (.");
        }
        NumericValueExpression inverseDistributionFunctionArgument = parseNumericValueExpression();
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        WithinGroupSpecification withinGroupSpecification = parseWithinGroupSpecification();
        return new InverseDistributionFunction(
                inverseDistributionFunctionType,
                inverseDistributionFunctionArgument,
                withinGroupSpecification
        );
    }

    // PERCENTILE_CONT | PERCENTILE_DISC
    private InverseDistributionFunctionType tryParseInverseDistributionFunctionType() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "PERCENTILE_CONT".equalsIgnoreCase(token.getContent())) {
            return new InverseDistributionFunctionType(token.getBeginIndex(), token.getEndIndex(), true, false);
        }
        if (token.isRegularIdentifier() && "PERCENTILE_DISC".equalsIgnoreCase(token.getContent())) {
            return new InverseDistributionFunctionType(token.getBeginIndex(), token.getEndIndex(), false, true);
        }
        reset(markedIndex);
        return null;
    }

    // <binary set function type> <left paren> <dependent variable expression> <comma> <independent variable expression> <right paren>
    private BinarySetFunction tryParseBinarySetFunction() {
        int markedIndex0 = mark();
        Token token0 = scan();
        reset(markedIndex0);
        BinarySetFunctionType binarySetFunctionType = tryParseBinarySetFunctionType();
        if (binarySetFunctionType == null) {
            return null;
        }
        Token token = scan();
        if (!token.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a (.");
        }
        NumericValueExpression dependentVariableExpression = parseNumericValueExpression();
        Token token1 = scan();
        if (!token1.isComma()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ,.");
        }
        NumericValueExpression independentVariableExpression = parseNumericValueExpression();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new BinarySetFunction(
                token0.getBeginIndex(),
                token2.getEndIndex(),
                binarySetFunctionType,
                dependentVariableExpression,
                independentVariableExpression
        );
    }

    private BinarySetFunctionType tryParseBinarySetFunctionType() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "COVAR_POP".equalsIgnoreCase(token.getContent())) {
            return BinarySetFunctionType.COVAR_POP;
        }
        if (token.isRegularIdentifier() && "COVAR_SAMP".equalsIgnoreCase(token.getContent())) {
            return BinarySetFunctionType.COVAR_SAMP;
        }
        if (token.isRegularIdentifier() && "CORR".equalsIgnoreCase(token.getContent())) {
            return BinarySetFunctionType.CORR;
        }
        if (token.isRegularIdentifier() && "REGR_SLOPE".equalsIgnoreCase(token.getContent())) {
            return BinarySetFunctionType.REGR_SLOPE;
        }
        if (token.isRegularIdentifier() && "REGR_INTERCEPT".equalsIgnoreCase(token.getContent())) {
            return BinarySetFunctionType.REGR_INTERCEPT;
        }
        if (token.isRegularIdentifier() && "REGR_COUNT".equalsIgnoreCase(token.getContent())) {
            return BinarySetFunctionType.REGR_COUNT;
        }
        if (token.isRegularIdentifier() && "REGR_R2".equalsIgnoreCase(token.getContent())) {
            return BinarySetFunctionType.REGR_R2;
        }
        if (token.isRegularIdentifier() && "REGR_AVGX".equalsIgnoreCase(token.getContent())) {
            return BinarySetFunctionType.REGR_AVGX;
        }
        if (token.isRegularIdentifier() && "REGR_AVGY".equalsIgnoreCase(token.getContent())) {
            return BinarySetFunctionType.REGR_AVGY;
        }
        if (token.isRegularIdentifier() && "REGR_SXX".equalsIgnoreCase(token.getContent())) {
            return BinarySetFunctionType.REGR_SXX;
        }
        if (token.isRegularIdentifier() && "REGR_SYY".equalsIgnoreCase(token.getContent())) {
            return BinarySetFunctionType.REGR_SYY;
        }
        if (token.isRegularIdentifier() && "REGR_SXY".equalsIgnoreCase(token.getContent())) {
            return BinarySetFunctionType.REGR_SXY;
        }
        reset(markedIndex);
        return null;
    }

    // <set function type> <left paren> [ <set quantifier> ] <value expression> <right paren>
    private GeneralSetFunction tryParseGeneralSetFunction() {
        ComputationalOperation setFunctionType = tryParseComputationalOperation();
        if (setFunctionType == null) {
            return null;
        }
        Token token = scan();
        if (!token.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a (.");
        }
        SetQuantifier setQuantifier = tryParseSetQuantifier();
        ValueExpression valueExpression = parseValueExpression();
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new GeneralSetFunction(token1.getEndIndex(), setFunctionType, setQuantifier, valueExpression);
    }

    private ComputationalOperation tryParseComputationalOperation() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "AVG".equalsIgnoreCase(token.getContent())) {
            return new ComputationalOperation(token.getBeginIndex(), token.getEndIndex(), ComputationalOperationType.AVG);
        }
        if (token.isRegularIdentifier() && "MAX".equalsIgnoreCase(token.getContent())) {
            return new ComputationalOperation(token.getBeginIndex(), token.getEndIndex(), ComputationalOperationType.MAX);
        }
        if (token.isRegularIdentifier() && "MIN".equalsIgnoreCase(token.getContent())) {
            return new ComputationalOperation(token.getBeginIndex(), token.getEndIndex(), ComputationalOperationType.MIN);
        }
        if (token.isRegularIdentifier() && "SUM".equalsIgnoreCase(token.getContent())) {
            return new ComputationalOperation(token.getBeginIndex(), token.getEndIndex(), ComputationalOperationType.SUM);
        }
        if (token.isRegularIdentifier() && "EVERY".equalsIgnoreCase(token.getContent())) {
            return new ComputationalOperation(token.getBeginIndex(), token.getEndIndex(), ComputationalOperationType.EVERY);
        }
        if (token.isAny()) {
            return new ComputationalOperation(token.getBeginIndex(), token.getEndIndex(), ComputationalOperationType.ANY);
        }
        if (token.isSome()) {
            return new ComputationalOperation(token.getBeginIndex(), token.getEndIndex(), ComputationalOperationType.SOME);
        }
        if (token.isRegularIdentifier() && "COUNT".equalsIgnoreCase(token.getContent())) {
            return new ComputationalOperation(token.getBeginIndex(), token.getEndIndex(), ComputationalOperationType.COUNT);
        }
        if (token.isRegularIdentifier() && "STDDEV_POP".equalsIgnoreCase(token.getContent())) {
            return new ComputationalOperation(token.getBeginIndex(), token.getEndIndex(), ComputationalOperationType.STDDEV_POP);
        }
        if (token.isRegularIdentifier() && "STDDEV_SAMP".equalsIgnoreCase(token.getContent())) {
            return new ComputationalOperation(token.getBeginIndex(), token.getEndIndex(), ComputationalOperationType.STDDEV_SAMP);
        }
        if (token.isRegularIdentifier() && "VAR_SAMP".equalsIgnoreCase(token.getContent())) {
            return new ComputationalOperation(token.getBeginIndex(), token.getEndIndex(), ComputationalOperationType.VAR_SAMP);
        }
        if (token.isRegularIdentifier() && "VAR_POP".equalsIgnoreCase(token.getContent())) {
            return new ComputationalOperation(token.getBeginIndex(), token.getEndIndex(), ComputationalOperationType.VAR_POP);
        }
        if (token.isRegularIdentifier() && "COLLECT".equalsIgnoreCase(token.getContent())) {
            return new ComputationalOperation(token.getBeginIndex(), token.getEndIndex(), ComputationalOperationType.COLLECT);
        }
        if (token.isRegularIdentifier() && "FUSION".equalsIgnoreCase(token.getContent())) {
            return new ComputationalOperation(token.getBeginIndex(), token.getEndIndex(), ComputationalOperationType.FUSION);
        }
        if (token.isRegularIdentifier() && "INTERSECTION".equalsIgnoreCase(token.getContent())) {
            return new ComputationalOperation(token.getBeginIndex(), token.getEndIndex(), ComputationalOperationType.INTERSECTION);
        }
        reset(markedIndex);
        return null;
    }

    private SetQuantifier parseSetQuantifier() {
        SetQuantifier setQuantifier = tryParseSetQuantifier();
        if (setQuantifier != null) {
            return setQuantifier;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to set quantifier.");
    }

    // DISTINCT | ALL
    private SetQuantifier tryParseSetQuantifier() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isDistinct()) {
            return new SetQuantifier(token.getBeginIndex(), token.getEndIndex(), true, false);
        }
        if (token.isAll()) {
            return new SetQuantifier(token.getBeginIndex(), token.getEndIndex(), false, true);
        }
        reset(markedIndex);
        return null;
    }

    // FILTER <left paren> WHERE <search condition> <right paren>
    private FilterClause tryParseFilterClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isFilter()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        Token token2 = scan();
        if (!token2.isWhere()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a WHERE.");
        }
        BooleanValueExpression searchCondition = parseBooleanValueExpression();
        Token token3 = scan();
        if (!token3.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
        }
        return new FilterClause(token.getBeginIndex(), token3.getEndIndex(), searchCondition);
    }

    private GroupingOperation parseGroupingOperation() {
        GroupingOperation groupingOperation = tryParseGroupingOperation();
        if (groupingOperation != null) {
            return groupingOperation;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to grouping operation.");
    }

    private GroupingOperation tryParseGroupingOperation() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isGrouping()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        ColumnReference columnReference = parseColumnReference();
        List<ColumnReference> columnReferenceList = new ArrayList<>();
        columnReferenceList.add(columnReference);
        while (true) {
            int markedIndex2 = mark();
            Token token2 = scan();
            if (!token2.isComma()) {
                reset(markedIndex2);
                break;
            }
            ColumnReference columnReference1 = parseColumnReference();
            columnReferenceList.add(columnReference1);
        }
        Token token3 = scan();
        if (!token3.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
        }
        return new GroupingOperation(token.getBeginIndex(), token3.getEndIndex(), columnReferenceList);
    }

    private WindowFunction parseWindowFunction() {
        WindowFunction windowFunction = tryParseWindowFunction();
        if (windowFunction != null) {
            return windowFunction;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to window function.");
    }

    private WindowFunction tryParseWindowFunction() {
        WindowFunctionType windowFunctionType = tryParseWindowFunctionType();
        if (windowFunctionType == null) {
            return null;
        }
        Token token = scan();
        if (!token.isOver()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a OVER.");
        }
        WindowNameOrSpecification windowNameOrSpecification = parseWindowNameOrSpecification();
        return new WindowFunction(windowFunctionType, windowNameOrSpecification);
    }

    private WindowFunctionType parseWindowFunctionType() {
        WindowFunctionType windowFunctionType = tryParseWindowFunctionType();
        if (windowFunctionType != null) {
            return windowFunctionType;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to window function type.");
    }

    private Subquery parseSubquery() {
        Subquery subquery = tryParseSubquery();
        if (subquery != null) {
            return subquery;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to subquery.");
    }

    private Subquery tryParseSubquery() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        QueryExpression queryExpression = parseQueryExpression();
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new Subquery(token.getBeginIndex(), token1.getEndIndex(), queryExpression);
    }

    private QueryExpression parseQueryExpression() {
        QueryExpression queryExpression = tryParseQueryExpression();
        if (queryExpression != null) {
            return queryExpression;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to query expression.");
    }

    private QueryExpression tryParseQueryExpression() {
        WithClause withClause = tryParseWithClause();
        QueryExpressionBody queryExpressionBody = parseQueryExpressionBody();
        return new QueryExpression(withClause, queryExpressionBody);
    }

    private WithClause tryParseWithClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isWith()) {
            reset(markedIndex);
            return null;
        }
        boolean recursive;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isRecursive()) {
            recursive = true;
        } else {
            reset(markedIndex1);
            recursive = false;
        }
        WithListElement withListElement = parseWithListElement();
        List<WithListElement> withList = new ArrayList<>();
        withList.add(withListElement);
        while (true) {
            int markedIndex2 = mark();
            Token token2 = scan();
            if (!token2.isComma()) {
                reset(markedIndex2);
                break;
            }
            WithListElement withListElement2 = parseWithListElement();
            withList.add(withListElement2);
        }
        return new WithClause(token.getBeginIndex(), recursive, new WithList(withList));
    }

    private WithListElement parseWithListElement() {
        Token token = scan();
        if (!token.isRegularIdentifier() && !token.isUnicodeDelimiterBody()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing query name.");
        }
        Identifier queryName = new Identifier(token);
        IdentifierList withColumnList;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isLeftParen()) {
            withColumnList = parseIdentifierList();
            Token token2 = scan();
            if (!token2.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
            }
        } else {
            reset(markedIndex1);
            withColumnList = null;
        }
        Token token3 = scan();
        if (!token3.isAs()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a AS.");
        }
        Token token4 = scan();
        if (!token4.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a (.");
        }
        QueryExpression queryExpression = parseQueryExpression();
        Token token5 = scan();
        if (!token5.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing a ).");
        }
        int endIndex;
        SearchOrCycleClause searchOrCycleClause = tryParseSearchOrCycleClause();
        if (searchOrCycleClause == null) {
            endIndex = token5.getEndIndex();
        } else {
            endIndex = searchOrCycleClause.getEndIndex();
        }
        return new WithListElement(endIndex, queryName, withColumnList, queryExpression, searchOrCycleClause);
    }

    private SearchOrCycleClause tryParseSearchOrCycleClause() {
        SearchClause searchClause = tryParseSearchClause();
        CycleClause cycleClause = tryParseCycleClause();
        if (searchClause == null && cycleClause == null) {
            return null;
        }
        int beginIndex;
        if (searchClause != null) {
            beginIndex = searchClause.getBeginIndex();
        } else {
            beginIndex = cycleClause.getBeginIndex();
        }
        int endIndex;
        if (cycleClause != null) {
            endIndex = cycleClause.getEndIndex();
        } else {
            endIndex = searchClause.getEndIndex();
        }
        return new SearchOrCycleClause(beginIndex, endIndex, searchClause, cycleClause);
    }

    private SearchClause tryParseSearchClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSearch()) {
            reset(markedIndex);
            return null;
        }
        RecursiveSearchOrder recursiveSearchOrder = parseRecursiveSearchOrder();
        Token token1 = scan();
        if (token1.isSet()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SET.");
        }
        Token token2 = scan();
        if (!token2.isRegularIdentifier() && !token2.isUnicodeDelimiterBody()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing sequence column.");
        }
        Identifier sequenceName = new Identifier(token2);
        return new SearchClause(token.getBeginIndex(), recursiveSearchOrder, sequenceName);
    }

    public RecursiveSearchOrder parseRecursiveSearchOrder() {
        Token token = scan();
        if (token.isRegularIdentifier() && "DEPTH".equalsIgnoreCase(token.getContent())) {
            Token token1 = scan();
            if (!token1.isRegularIdentifier() && "FIRST".equalsIgnoreCase(token1.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a FIRST.");
            }
            Token token2 = scan();
            if (!token2.isBy()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a BY.");
            }
            SortSpecificationList sortSpecificationList = parseSortSpecificationList();
            return new RecursiveSearchOrder(token.getBeginIndex(), true, false, sortSpecificationList);
        }
        if (token.isRegularIdentifier() && "BREADTH".equalsIgnoreCase(token.getContent())) {
            Token token1 = scan();
            if (!token1.isRegularIdentifier() && "FIRST".equalsIgnoreCase(token1.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a FIRST.");
            }
            Token token2 = scan();
            if (!token2.isBy()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a BY.");
            }
            SortSpecificationList sortSpecificationList = parseSortSpecificationList();
            return new RecursiveSearchOrder(token.getBeginIndex(), false, true, sortSpecificationList);
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to recursive search order.");
    }

    private SortSpecificationList parseSortSpecificationList() {
        SortSpecification sortSpecification = parseSortSpecification();
        List<SortSpecification> sortSpecificationList = new ArrayList<>();
        sortSpecificationList.add(sortSpecification);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isComma()) {
                reset(markedIndex);
                break;
            }
            SortSpecification sortSpecification1 = parseSortSpecification();
            sortSpecificationList.add(sortSpecification1);
        }
        return new SortSpecificationList(sortSpecificationList);
    }

    private SortSpecification parseSortSpecification() {
        ValueExpression sortKey = parseValueExpression();
        OrderingSpecification orderingSpecification = tryParseOrderingSpecification();
        NullOrdering nullOrdering = tryParseNullOrdering();
        return new SortSpecification(sortKey, orderingSpecification, nullOrdering);
    }

    private OrderingSpecification tryParseOrderingSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "ASC".equalsIgnoreCase(token.getContent())) {
            return new OrderingSpecification(token.getBeginIndex(), token.getEndIndex(), true, false);
        }
        if (token.isRegularIdentifier() && "DESC".equalsIgnoreCase(token.getContent())) {
            return new OrderingSpecification(token.getBeginIndex(), token.getEndIndex(), false, true);
        }
        reset(markedIndex);
        return null;
    }

    private NullOrdering tryParseNullOrdering() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"NULLS".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (token1.isRegularIdentifier() && "FIRST".equalsIgnoreCase(token1.getContent())) {
            return new NullOrdering(token.getBeginIndex(), token1.getEndIndex(), true, false);
        }
        if (token1.isRegularIdentifier() && "LAST".equalsIgnoreCase(token1.getContent())) {
            return new NullOrdering(token.getBeginIndex(), token1.getEndIndex(), false, true);
        }
        throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing FIRST or LAST.");
    }

    //  CYCLE <cycle column list>
    //  SET <cycle mark column> TO <cycle mark value>
    //  DEFAULT <non-cycle mark value>
    //  USING <path column>
    private CycleClause tryParseCycleClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCycle()) {
            reset(markedIndex);
            return null;
        }
        IdentifierList cycleColumnList = parseIdentifierList();
        Token token1 = scan();
        if (!token1.isSet()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SET.");
        }
        Identifier cycleMarkColumn = parseIdentifier();
        Token token2 = scan();
        if (!token2.isTo()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a TO.");
        }
        ValueExpression cycleMarkValue = parseValueExpression();
        Token token3 = scan();
        if (!token3.isDefault()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a DEFAULT.");
        }
        ValueExpression nonCycleMarkValue = parseValueExpression();
        Token token4 = scan();
        if (!token4.isUsing()) {
            throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a USING.");
        }
        Identifier pathColumn = parseIdentifier();
        return new CycleClause(
                token.getBeginIndex(),
                cycleColumnList,
                cycleMarkColumn,
                cycleMarkValue,
                nonCycleMarkValue,
                pathColumn);
    }

    private QueryExpressionBody parseQueryExpressionBody() {
        QueryExpressionBody queryExpressionBody = tryParseQueryExpressionBody();
        if (queryExpressionBody != null) {
            return queryExpressionBody;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to query expression body.");
    }

    private QueryExpressionBody tryParseQueryExpressionBody() {
        NonJoinQueryExpression nonJoinQueryExpression = tryParseNonJoinQueryExpression();
        if (nonJoinQueryExpression != null) {
            return nonJoinQueryExpression;
        }
        JoinedTable joinedTable = tryParseJoinedTable();
        if (joinedTable != null) {
            return joinedTable;
        }
        return null;
    }

    private NonJoinQueryExpression parseNonJoinQueryExpression() {
        NonJoinQueryExpression nonJoinQueryExpression = tryParseNonJoinQueryExpression();
        if (nonJoinQueryExpression != null) {
            return nonJoinQueryExpression;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to non-join query expression.");
    }

    //   <non-join query term>
    // | <query expression body> UNION [ ALL | DISTINCT ] [ <corresponding spec> ] <query term>
    // | <query expression body> EXCEPT [ ALL | DISTINCT ] [ <corresponding spec> ] <query term>
    private NonJoinQueryExpression tryParseNonJoinQueryExpression() {
        NonJoinQueryExpression nonJoinQueryExpression = tryParseNonJoinQueryTerm();
        if (nonJoinQueryExpression != null) {
            while (true) {
                boolean union;
                int markedIndex = mark();
                Token token = scan();
                if (token.isUnion()) {
                    union = true;
                } else if (token.isExcept()) {
                    union = false;
                } else {
                    reset(markedIndex);
                    break;
                }
                boolean all = false;
                boolean distinct = false;
                int markedIndex1 = mark();
                Token token1 = scan();
                if (token1.isAll()) {
                    all = true;
                } else if (token1.isDistinct()) {
                    distinct = true;
                } else {
                    reset(markedIndex1);
                }
                CorrespondingSpec correspondingSpec = tryParseCorrespondingSpec();
                QueryTerm queryTerm = parseQueryTerm();
                if (union == true) {
                    nonJoinQueryExpression = new UnionNonJoinQueryExpression(
                            nonJoinQueryExpression,
                            all,
                            distinct,
                            correspondingSpec,
                            queryTerm
                    );
                } else {
                    nonJoinQueryExpression = new ExceptNonJoinQueryExpression(
                            nonJoinQueryExpression,
                            all,
                            distinct,
                            correspondingSpec,
                            queryTerm
                    );
                }
            }
            return nonJoinQueryExpression;
        }
        int markedIndex = mark();
        JoinedTable joinedTable = tryParseJoinedTable();
        if (joinedTable == null) {
            return null;
        }
        int markedIndex1 = mark();
        Token token1 = scan();
        reset(markedIndex1);
        if (!token1.isUnion() && !token1.isExcept()) {
            reset(markedIndex);
            return null;
        }
        while (true) {
            boolean union;
            int markedIndex2 = mark();
            Token token2 = scan();
            if (token2.isUnion()) {
                union = true;
            } else if (token2.isExcept()) {
                union = false;
            } else {
                reset(markedIndex2);
                break;
            }
            boolean all = false;
            boolean distinct = false;
            int markedIndex3 = mark();
            Token token3 = scan();
            if (token3.isAll()) {
                all = true;
            } else if (token3.isDistinct()) {
                distinct = true;
            } else {
                reset(markedIndex3);
            }
            CorrespondingSpec correspondingSpec = tryParseCorrespondingSpec();
            QueryTerm queryTerm = parseQueryTerm();
            if (union == true) {
                if (nonJoinQueryExpression == null) {
                    nonJoinQueryExpression = new UnionNonJoinQueryExpression(
                            joinedTable,
                            all,
                            distinct,
                            correspondingSpec,
                            queryTerm
                    );
                } else {
                    nonJoinQueryExpression = new UnionNonJoinQueryExpression(
                            nonJoinQueryExpression,
                            all,
                            distinct,
                            correspondingSpec,
                            queryTerm
                    );
                }
            } else {
                if (nonJoinQueryExpression == null) {
                    nonJoinQueryExpression = new ExceptNonJoinQueryExpression(
                            joinedTable,
                            all,
                            distinct,
                            correspondingSpec,
                            queryTerm
                    );
                } else {
                    nonJoinQueryExpression = new ExceptNonJoinQueryExpression(
                            nonJoinQueryExpression,
                            all,
                            distinct,
                            correspondingSpec,
                            queryTerm
                    );
                }
            }
        }
        return nonJoinQueryExpression;
    }

    private QueryTerm parseQueryTerm() {
        QueryTerm queryTerm = tryParseQueryTerm();
        if (queryTerm != null) {
            return queryTerm;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to query term.");
    }

    // <non-join query term> | <joined table>
    private QueryTerm tryParseQueryTerm() {
        NonJoinQueryTerm nonJoinQueryTerm = tryParseNonJoinQueryTerm();
        if (nonJoinQueryTerm != null) {
            return nonJoinQueryTerm;
        }
        JoinedTable joinedTable = tryParseJoinedTable();
        if (joinedTable != null) {
            return joinedTable;
        }
        return null;
    }

    //   <non-join query primary>
    // | <query term> INTERSECT [ ALL | DISTINCT ] [ <corresponding spec> ] <query primary>
    private NonJoinQueryTerm tryParseNonJoinQueryTerm() {
        NonJoinQueryTerm nonJoinQueryTerm = tryParseNonJoinQueryPrimary();
        if (nonJoinQueryTerm != null) {
            while (true) {
                int markedIndex = mark();
                Token token = scan();
                if (!token.isIntersect()) {
                    reset(markedIndex);
                    break;
                }
                boolean all = false;
                boolean distinct = false;
                int markedIndex1 = mark();
                Token token1 = scan();
                if (token1.isAll()) {
                    all = true;
                } else if (token1.isDistinct()) {
                    distinct = true;
                } else {
                    reset(markedIndex1);
                }
                CorrespondingSpec correspondingSpec = tryParseCorrespondingSpec();
                QueryPrimary queryPrimary = parseQueryPrimary();
                nonJoinQueryTerm = new IntersectNonJoinQueryTerm(
                        nonJoinQueryTerm,
                        all,
                        distinct,
                        correspondingSpec,
                        queryPrimary
                );
            }
            return nonJoinQueryTerm;
        }
        int markedIndex = mark();
        JoinedTable joinedTable = tryParseJoinedTable();
        if (joinedTable == null) {
            return null;
        }
        int markedIndex1 = mark();
        Token token1 = scan();
        reset(markedIndex1);
        if (!token1.isIntersect()) {
            reset(markedIndex);
            return null;
        }
        while (true) {
            int markedIndex2 = mark();
            Token token2 = scan();
            if (!token2.isIntersect()) {
                reset(markedIndex2);
                break;
            }
            boolean all = false;
            boolean distinct = false;
            int markedIndex3 = mark();
            Token token3 = scan();
            if (token3.isAll()) {
                all = true;
            } else if (token3.isDistinct()) {
                distinct = true;
            } else {
                reset(markedIndex3);
            }
            CorrespondingSpec correspondingSpec = tryParseCorrespondingSpec();
            QueryPrimary queryPrimary = parseQueryPrimary();
            if (nonJoinQueryTerm == null) {
                nonJoinQueryTerm = new IntersectNonJoinQueryTerm(
                        joinedTable,
                        all,
                        distinct,
                        correspondingSpec,
                        queryPrimary
                );
            } else {
                nonJoinQueryTerm = new IntersectNonJoinQueryTerm(
                        nonJoinQueryTerm,
                        all,
                        distinct,
                        correspondingSpec,
                        queryPrimary
                );
            }
        }
        return nonJoinQueryTerm;
    }

    private QueryPrimary parseQueryPrimary() {
        NonJoinQueryPrimary nonJoinQueryPrimary = tryParseNonJoinQueryPrimary();
        if (nonJoinQueryPrimary != null) {
            return nonJoinQueryPrimary;
        }
        JoinedTable joinedTable = tryParseJoinedTable();
        if (joinedTable != null) {
            return joinedTable;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to query primary.");
    }

    //   <simple table>
    // | <left paren> <non-join query expression> <right paren>
    private NonJoinQueryPrimary tryParseNonJoinQueryPrimary() {
        SimpleTable simpleTable = tryParseSimpleTable();
        if (simpleTable != null) {
            return simpleTable;
        }
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        NonJoinQueryExpression nonJoinQueryExpression = parseNonJoinQueryExpression();
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new NonJoinQueryExpressionNonJoinQueryPrimary(
                token.getBeginIndex(),
                token1.getEndIndex(),
                nonJoinQueryExpression
        );
    }

    private CorrespondingSpec parseCorrespondingSpec() {
        CorrespondingSpec correspondingSpec = tryParseCorrespondingSpec();
        if (correspondingSpec != null) {
            return correspondingSpec;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to corresponding spec.");
    }

    private CorrespondingSpec tryParseCorrespondingSpec() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCorresponding()) {
            reset(markedIndex);
            return null;
        }
        int markedIndex1 = mark();
        Token token1 = scan();
        if (!token1.isBy()) {
            reset(markedIndex1);
            return new CorrespondingSpec(token.getBeginIndex(), token.getEndIndex(), null);
        }
        Token token2 = scan();
        if (!token2.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a (.");
        }
        IdentifierList correspondingColumnList = parseIdentifierList();
        Token token3 = scan();
        if (!token3.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
        }
        return new CorrespondingSpec(token.getBeginIndex(), token3.getEndIndex(), correspondingColumnList);
    }

    //   <rank function type> <left paren> <right paren>
    // | ROW_NUMBER <left paren> <right paren>
    // | <aggregate function>
    private WindowFunctionType tryParseWindowFunctionType() {
        RankFunctionTypeWindowFunctionType rankFunctionTypeWindowFunctionType
                = tryParseRankFunctionTypeWindowFunctionType();
        if (rankFunctionTypeWindowFunctionType != null) {
            return rankFunctionTypeWindowFunctionType;
        }
        RowNumberWindowFunctionType rowNumberWindowFunctionType
                = tryParseRowNumberWindowFunctionType();
        if (rowNumberWindowFunctionType != null) {
            return rowNumberWindowFunctionType;
        }
        AggregateFunction aggregateFunction = tryParseAggregateFunction();
        if (aggregateFunction != null) {
            return aggregateFunction;
        }
        return null;
    }

    private RankFunctionTypeWindowFunctionType tryParseRankFunctionTypeWindowFunctionType() {
        RankFunctionType rankFunctionType = tryParseRankFunctionType();
        if (rankFunctionType == null) {
            return null;
        }
        Token token = scan();
        if (!token.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a (.");
        }
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new RankFunctionTypeWindowFunctionType(token1.getEndIndex(), rankFunctionType);
    }

    private RowNumberWindowFunctionType tryParseRowNumberWindowFunctionType() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"ROW_NUMBER".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new RowNumberWindowFunctionType(token.getBeginIndex(), token2.getEndIndex());
    }

    // RANK | DENSE_RANK | PERCENT_RANK | CUME_DIST
    private RankFunctionType tryParseRankFunctionType() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier()) {
            if ("RANK".equalsIgnoreCase(token.getContent())) {
                return new RankFunctionType(token.getBeginIndex(), token.getEndIndex(), true, false, false, false);
            }
            if ("DENSE_RANK".equalsIgnoreCase(token.getContent())) {
                return new RankFunctionType(token.getBeginIndex(), token.getEndIndex(), false, true, false, false);
            }
            if ("PERCENT_RANK".equalsIgnoreCase(token.getContent())) {
                return new RankFunctionType(token.getBeginIndex(), token.getEndIndex(), false, false, true, false);
            }
            if ("CUME_DIST".equalsIgnoreCase(token.getContent())) {
                return new RankFunctionType(token.getBeginIndex(), token.getEndIndex(), false, false, false, true);
            }
        }
        reset(markedIndex);
        return null;
    }

    private WindowNameOrSpecification parseWindowNameOrSpecification() {
        WindowNameOrSpecification windowNameOrSpecification = tryParseWindowNameOrSpecification();
        if (windowNameOrSpecification != null) {
            return windowNameOrSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to window name or specification.");
    }

    private WindowNameOrSpecification tryParseWindowNameOrSpecification() {
        WindowNameOrSpecification windowNameOrSpecification = tryParseIdentifier();
        if (windowNameOrSpecification == null) {
            windowNameOrSpecification = tryParseWindowSpecification();
        }
        return windowNameOrSpecification;
    }

    private WindowSpecification parseWindowSpecification() {
        WindowSpecification windowSpecification = tryParseWindowSpecification();
        if (windowSpecification != null) {
            return windowSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to window specification.");
    }

    // <left paren> [ <existing window name> ] [ <window partition clause> ] [ <window order clause> ] [ <window frame clause> ] <right paren>
    private WindowSpecification tryParseWindowSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        Identifier existingWindowName = tryParseIdentifier();
        WindowPartitionClause windowPartitionClause = tryParseWindowPartitionClause();
        WindowOrderClause windowOrderClause = tryParseWindowOrderClause();
        WindowFrameClause windowFrameClause = tryParseWindowFrameClause();
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new WindowSpecification(
                token.getBeginIndex(),
                token1.getEndIndex(),
                existingWindowName,
                windowPartitionClause,
                windowOrderClause,
                windowFrameClause
        );
    }

    private WindowPartitionClause parseWindowPartitionClause() {
        WindowPartitionClause windowPartitionClause = tryParseWindowPartitionClause();
        if (windowPartitionClause != null) {
            return windowPartitionClause;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to window partition clause.");
    }

    private WindowPartitionClause tryParseWindowPartitionClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isPartition()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isBy()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a BY.");
        }
        WindowPartitionColumnReference windowPartitionColumnReference
                = parseWindowPartitionColumnReference();
        List<WindowPartitionColumnReference> windowPartitionColumnReferenceList
                = new ArrayList<>();
        windowPartitionColumnReferenceList.add(windowPartitionColumnReference);
        while (true) {
            int markedIndex2 = mark();
            Token token2 = scan();
            if (!token2.isComma()) {
                reset(markedIndex2);
                break;
            }
            WindowPartitionColumnReference windowPartitionColumnReference1
                    = parseWindowPartitionColumnReference();
            windowPartitionColumnReferenceList.add(windowPartitionColumnReference1);
        }
        return new WindowPartitionClause(
                token.getBeginIndex(),
                new WindowPartitionColumnReferenceList(windowPartitionColumnReferenceList)
        );
    }

    private WindowPartitionColumnReference parseWindowPartitionColumnReference() {
        ColumnReference columnReference = parseColumnReference();
        CollateClause collateClause = tryParseCollateClause();
        return new WindowPartitionColumnReference(columnReference, collateClause);
    }

    // ORDER BY <sort specification list>
    private WindowOrderClause tryParseWindowOrderClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isOrder()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isBy()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a BY.");
        }
        SortSpecificationList sortSpecificationList = parseSortSpecificationList();
        return new WindowOrderClause(token.getBeginIndex(), sortSpecificationList);
    }

    private WindowFrameClause tryParseWindowFrameClause() {
        WindowFrameUnits windowFrameUnits = tryParseWindowFrameUnits();
        if (windowFrameUnits == null) {
            return null;
        }
        WindowFrameExtent windowFrameExtent = parseWindowFrameExtent();
        WindowFrameExclusion windowFrameExclusion = tryParseWindowFrameExclusion();
        return new WindowFrameClause(windowFrameUnits, windowFrameExtent, windowFrameExclusion);
    }

    private WindowFrameUnits tryParseWindowFrameUnits() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isRows()) {
            return new WindowFrameUnits(token.getBeginIndex(), token.getEndIndex(), true, false);
        }
        if (token.isRange()) {
            return new WindowFrameUnits(token.getBeginIndex(), token.getEndIndex(), false, true);
        }
        reset(markedIndex);
        return null;
    }

    private WindowFrameExtent parseWindowFrameExtent() {
        WindowFrameExtent windowFrameExtent = tryParseWindowFrameStart();
        if (windowFrameExtent != null) {
            return windowFrameExtent;
        }
        windowFrameExtent = tryParseWindowFrameBetween();
        if (windowFrameExtent != null) {
            return windowFrameExtent;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to window frame extent.");
    }

    private WindowFrameStart tryParseWindowFrameStart() {
        WindowFrameStart windowFrameStart = tryParseUnboundedPreceding();
        if (windowFrameStart != null) {
            return windowFrameStart;
        }
        windowFrameStart = tryParseWindowFramePreceding();
        if (windowFrameStart != null) {
            return windowFrameStart;
        }
        windowFrameStart = tryParseCurrentRow();
        return windowFrameStart;
    }

    // UNBOUNDED PRECEDING
    private UnboundedPreceding tryParseUnboundedPreceding() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"UNBOUNDED".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"PRECEDING".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a PRECEDING.");
        }
        return new UnboundedPreceding(token.getBeginIndex(), token1.getEndIndex());
    }

    // <unsigned value specification> PRECEDING
    private WindowFramePreceding tryParseWindowFramePreceding() {
        UnsignedValueSpecification unsignedValueSpecification = tryParseUnsignedValueSpecification();
        if (unsignedValueSpecification == null) {
            return null;
        }
        Token token = scan();
        if (!token.isRegularIdentifier() || !"PRECEDING".equalsIgnoreCase(token.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a PRECEDING.");
        }
        return new WindowFramePreceding(token.getEndIndex(), unsignedValueSpecification);
    }

    // CURRENT ROW
    private CurrentRow tryParseCurrentRow() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCurrent()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRow()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ROW.");
        }
        return new CurrentRow(token.getBeginIndex(), token1.getEndIndex());
    }

    private WindowFrameBetween tryParseWindowFrameBetween() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isBetween()) {
            reset(markedIndex);
            return null;
        }
        WindowFrameBound windowFrameBound1 = parseWindowFrameBound();
        Token token1 = scan();
        if (!token1.isAnd()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a AND.");
        }
        WindowFrameBound windowFrameBound2 = parseWindowFrameBound();
        return new WindowFrameBetween(token.getBeginIndex(), windowFrameBound1, windowFrameBound2);
    }

    private WindowFrameBound parseWindowFrameBound() {
        WindowFrameBound windowFrameBound = tryParseWindowFrameStart();
        if (windowFrameBound != null) {
            return windowFrameBound;
        }
        windowFrameBound = tryParseUnboundedFollowing();
        if (windowFrameBound != null) {
            return windowFrameBound;
        }
        windowFrameBound = tryParseWindowFrameFollowing();
        if (windowFrameBound != null) {
            return windowFrameBound;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to window frame bound.");
    }

    private UnboundedFollowing tryParseUnboundedFollowing() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"UNBOUNDED".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"FOLLOWING".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a FOLLOWING.");
        }
        return new UnboundedFollowing(token.getBeginIndex(), token1.getEndIndex());
    }

    private WindowFrameFollowing tryParseWindowFrameFollowing() {
        UnsignedValueSpecification unsignedValueSpecification = tryParseUnsignedValueSpecification();
        if (unsignedValueSpecification == null) {
            return null;
        }
        Token token = scan();
        if (!token.isRegularIdentifier() || !"FOLLOWING".equalsIgnoreCase(token.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a FOLLOWING.");
        }
        return new WindowFrameFollowing(token.getEndIndex(), unsignedValueSpecification);
    }

    // EXCLUDE CURRENT ROW | EXCLUDE GROUP | EXCLUDE TIES | EXCLUDE NO OTHERS
    private WindowFrameExclusion tryParseWindowFrameExclusion() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"EXCLUDE".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (token1.isCurrent()) {
            Token token2 = scan();
            if (!token2.isRow()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ROW.");
            }
            return new WindowFrameExclusion(
                    token.getBeginIndex(),
                    token2.getEndIndex(),
                    true, false, false, false);
        }
        if (token1.isGroup()) {
            return new WindowFrameExclusion(
                    token.getBeginIndex(),
                    token1.getEndIndex(),
                    false, true, false, false);
        }
        if (token1.isRegularIdentifier() && "TIES".equalsIgnoreCase(token1.getContent())) {
            return new WindowFrameExclusion(
                    token.getBeginIndex(),
                    token1.getEndIndex(),
                    false, false, true, false);
        }
        if (token1.isNo()) {
            Token token2 = scan();
            if (!token2.isRegularIdentifier() || !"".equalsIgnoreCase(token2.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a OTHERS.");
            }
            return new WindowFrameExclusion(
                    token.getBeginIndex(),
                    token1.getEndIndex(),
                    false, false, false, true);
        }
        reset(markedIndex);
        return null;
    }

    private ContextuallyTypedRowValueConstructor parseContextuallyTypedRowValueConstructor() {
        ContextuallyTypedRowValueConstructor contextuallyTypedRowValueConstructor = tryParseContextuallyTypedRowValueConstructor();
        if (contextuallyTypedRowValueConstructor != null) {
            return contextuallyTypedRowValueConstructor;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to contextually typed row value constructor.");
    }

    private ContextuallyTypedRowValueConstructor tryParseContextuallyTypedRowValueConstructor() {
        ContextuallyTypedRowValueConstructor contextuallyTypedRowValueConstructor = tryParseCommonValueExpression();
        if (contextuallyTypedRowValueConstructor == null) {
            contextuallyTypedRowValueConstructor = tryParseBooleanValueExpression();
        }
        if (contextuallyTypedRowValueConstructor == null) {
            contextuallyTypedRowValueConstructor = tryParseContextuallyTypedValueSpecification();
        }
        if (contextuallyTypedRowValueConstructor == null) {
            contextuallyTypedRowValueConstructor = tryParseRowContextuallyTypedRowValueConstructorElementList();
        }
        return contextuallyTypedRowValueConstructor;
    }

    private RowContextuallyTypedRowValueConstructorElementList parseRowContextuallyTypedRowValueConstructorElementList() {
        RowContextuallyTypedRowValueConstructorElementList rowContextuallyTypedRowValueConstructorElementList
                = tryParseRowContextuallyTypedRowValueConstructorElementList();
        if (rowContextuallyTypedRowValueConstructorElementList != null) {
            return rowContextuallyTypedRowValueConstructorElementList;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to contextually typed row value constructor element list.");
    }

    private RowContextuallyTypedRowValueConstructorElementList tryParseRowContextuallyTypedRowValueConstructorElementList() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isRow()) {
            Token token1 = scan();
            if (!token1.isLeftParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
            }
            ContextuallyTypedRowValueConstructorElement contextuallyTypedRowValueConstructorElement
                    = parseContextuallyTypedRowValueConstructorElement();
            List<ContextuallyTypedRowValueConstructorElement> contextuallyTypedRowValueConstructorElementList
                    = new ArrayList<>();
            contextuallyTypedRowValueConstructorElementList.add(contextuallyTypedRowValueConstructorElement);
            while (true) {
                int markedIndex2 = mark();
                Token token2 = scan();
                if (!token2.isComma()) {
                    reset(markedIndex2);
                    break;
                }
                ContextuallyTypedRowValueConstructorElement contextuallyTypedRowValueConstructorElement1
                        = parseContextuallyTypedRowValueConstructorElement();
                contextuallyTypedRowValueConstructorElementList.add(contextuallyTypedRowValueConstructorElement1);
            }
            Token token3 = scan();
            if (!token3.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
            }
            return new RowContextuallyTypedRowValueConstructorElementList(
                    token.getBeginIndex(),
                    token3.getEndIndex(),
                    true,
                    contextuallyTypedRowValueConstructorElementList);
        }
        if (token.isLeftParen()) {
            ContextuallyTypedRowValueConstructorElement contextuallyTypedRowValueConstructorElement
                    = parseContextuallyTypedRowValueConstructorElement();
            List<ContextuallyTypedRowValueConstructorElement> contextuallyTypedRowValueConstructorElementList
                    = new ArrayList<>();
            contextuallyTypedRowValueConstructorElementList.add(contextuallyTypedRowValueConstructorElement);
            Token token1 = scan();
            if (!token1.isComma()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ,.");
            }
            ContextuallyTypedRowValueConstructorElement contextuallyTypedRowValueConstructorElement1
                    = parseContextuallyTypedRowValueConstructorElement();
            contextuallyTypedRowValueConstructorElementList.add(contextuallyTypedRowValueConstructorElement1);
            while (true) {
                int markedIndex2 = mark();
                Token token2 = scan();
                if (!token2.isComma()) {
                    reset(markedIndex2);
                    break;
                }
                ContextuallyTypedRowValueConstructorElement contextuallyTypedRowValueConstructorElement2
                        = parseContextuallyTypedRowValueConstructorElement();
                contextuallyTypedRowValueConstructorElementList.add(contextuallyTypedRowValueConstructorElement2);
            }
            Token token3 = scan();
            if (!token3.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
            }
            return new RowContextuallyTypedRowValueConstructorElementList(
                    token.getBeginIndex(),
                    token3.getEndIndex(),
                    false,
                    contextuallyTypedRowValueConstructorElementList);
        }
        reset(markedIndex);
        return null;
    }

    private ContextuallyTypedRowValueConstructorElement parseContextuallyTypedRowValueConstructorElement() {
        ContextuallyTypedRowValueConstructorElement contextuallyTypedRowValueConstructorElement
                = tryParseContextuallyTypedRowValueConstructorElement();
        if (contextuallyTypedRowValueConstructorElement != null) {
            return contextuallyTypedRowValueConstructorElement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to contextually typed row value constructor element.");
    }

    private ContextuallyTypedRowValueConstructorElement tryParseContextuallyTypedRowValueConstructorElement() {
        ContextuallyTypedRowValueConstructorElement contextuallyTypedRowValueConstructorElement = tryParseValueExpression();
        if (contextuallyTypedRowValueConstructorElement == null) {
            contextuallyTypedRowValueConstructorElement = tryParseContextuallyTypedValueSpecification();
        }
        return contextuallyTypedRowValueConstructorElement;
    }

    private SetTarget parseSetTarget() {
        SetTarget setTarget = tryParseSetTarget();
        if (setTarget != null) {
            return setTarget;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to set target.");
    }

    private SetTarget tryParseSetTarget() {
        Identifier identifier = tryParseIdentifier();
        if (identifier == null) {
            return null;
        }
        int markedIndex = mark();
        Token token = scan();
        if (token.isPeriod()) {
            List<Identifier> identifierList = new ArrayList<>();
            identifierList.add(identifier);
            while (true) {
                Identifier identifier1 = parseIdentifier();
                identifierList.add(identifier1);
                int markedIndex1 = mark();
                Token token1 = scan();
                if (!token1.isPeriod()) {
                    reset(markedIndex1);
                    break;
                }
            }
            return new MutatedSetClause(identifierList);
        }
        int endIndex;
        SimpleValueSpecification simpleValueSpecification;
        if (token.isLeftBracketTrigraph()) {
            simpleValueSpecification = parseSimpleValueSpecification();
            Token token1 = scan();
            if (!token1.isRightBracketTrigraph()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ??).");
            }
            endIndex = token1.getEndIndex();
        } else {
            reset(markedIndex);
            simpleValueSpecification = null;
            endIndex = identifier.getEndIndex();
        }
        return new UpdateTarget(endIndex, identifier, simpleValueSpecification);
    }

    private Identifier parseIdentifier() {
        Identifier identifier = tryParseIdentifier();
        if (identifier != null) {
            return identifier;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing an identifier.");
    }

    private Identifier tryParseIdentifier() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() && !token.isUnicodeDelimiterBody()) {
            reset(markedIndex);
            return null;
        }
        return new Identifier(token);
    }

    private TargetTable parseTargetTable() {
        TargetTable targetTable = tryParseTargetTable();
        if (targetTable != null) {
            return targetTable;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to target table.");
    }

    private TargetTable tryParseTargetTable() {
        boolean only;
        LocalOrSchemaQualifiedName tableName;
        int endIndex;
        int markedIndex = mark();
        Token token = scan();
        if (token.isOnly()) {
            Token token1 = scan();
            if (!token1.isLeftParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
            }
            tableName = parseLocalOrSchemaQualifiedName();
            Token token2 = scan();
            if (!token2.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
            }
            only = true;
            endIndex = token2.getEndIndex();
        } else {
            reset(markedIndex);
            only = false;
            tableName = tryParseLocalOrSchemaQualifiedName();
            if (tableName == null) {
                return null;
            }
            endIndex = tableName.getEndIndex();
        }
        return new TargetTable(token.getBeginIndex(), endIndex, only, tableName);
    }

    private DynamicCursorName parseDynamicCursorName() {
        int markedIndex = mark();
        LocalQualifiedName cursorName = tryParseLocalQualifiedName();
        if (cursorName != null) {
            if (cursorName.isModule()) {
                return cursorName;
            }
            Token token = scan();
            if (!token.isPeriod()) {
                return cursorName;
            }
            reset(markedIndex);
        }
        return parseExtendedCursorName();
    }

    private ExtendedCursorName parseExtendedCursorName() {
        ExtendedCursorName extendedCursorName = tryParseExtendedCursorName();
        if (extendedCursorName != null) {
            return extendedCursorName;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to extended cursor name.");
    }

    private ExtendedCursorName tryParseExtendedCursorName() {
        ScopeOption scopeOption = tryParseScopeOption();
        SimpleValueSpecification simpleValueSpecification = parseSimpleValueSpecification();
        int beginIndex;
        if (scopeOption != null) {
            beginIndex = scopeOption.getBeginIndex();
        } else {
            beginIndex = simpleValueSpecification.getBeginIndex();
        }
        return new ExtendedCursorName(
                beginIndex,
                simpleValueSpecification.getEndIndex(),
                scopeOption,
                simpleValueSpecification
        );
    }

    private ScopeOption tryParseScopeOption() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isGlobal()) {
            return new ScopeOption(token.getBeginIndex(), token.getEndIndex(), true, false);
        }
        if (token.isLocal()) {
            return new ScopeOption(token.getBeginIndex(), token.getEndIndex(), false, true);
        }
        reset(markedIndex);
        return null;
    }

    private AssertionDefinition parseAssertionDefinition() {
        AssertionDefinition assertionDefinition = tryParseAssertionDefinition();
        if (assertionDefinition != null) {
            return assertionDefinition;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to assertion definition.");
    }

    private CollationDefinition parseCollationDefinition() {
        CollationDefinition collationDefinition = tryParseCollationDefinition();
        if (collationDefinition != null) {
            return collationDefinition;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to collation definition.");
    }

    private CollationDefinition tryParseCollationDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCreate()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"COLLATION".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName collationName = parseSchemaQualifiedName();
        Token token2 = scan();
        if (!token2.isFor()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a FOR.");
        }
        CharacterSetName characterSetSpecification = parseCharacterSetName();
        Token token3 = scan();
        if (!token3.isFrom()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a FROM.");
        }
        SchemaQualifiedName existingCollationName = parseSchemaQualifiedName();
        int endIndex = existingCollationName.getEndIndex();
        boolean noPad = false;
        boolean padSpace = false;
        int markedIndex4 = mark();
        Token token4 = scan();
        if (token4.isNo()) {
            Token token5 = scan();
            if (!token5.isRegularIdentifier() || !"PAD".equalsIgnoreCase(token5.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing a PAD.");
            }
            endIndex = token5.getEndIndex();
            noPad = true;
        } else if (token4.isRegularIdentifier() && "PAD".equalsIgnoreCase(token4.getContent())) {
            Token token5 = scan();
            if (!token5.isRegularIdentifier() || !"SPACE".equalsIgnoreCase(token5.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing a SPACE.");
            }
            endIndex = token5.getEndIndex();
            padSpace = true;
        } else {
            reset(markedIndex4);
        }
        return new CollationDefinition(
                token.getBeginIndex(),
                endIndex,
                collationName,
                characterSetSpecification,
                existingCollationName,
                noPad,
                padSpace);
    }

    private DomainDefinition parseDomainDefinition() {
        DomainDefinition domainDefinition = tryParseDomainDefinition();
        if (domainDefinition != null) {
            return domainDefinition;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to domain definition.");
    }

    private SchemaDefinition parseSchemaDefinition() {
        SchemaDefinition schemaDefinition = tryParseSchemaDefinition();
        if (schemaDefinition != null) {
            return schemaDefinition;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to schema definition.");
    }

    // CREATE SCHEMA <schema name clause> [ <schema character set or path> ] [ <schema element> ... ]
    private SchemaDefinition tryParseSchemaDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCreate()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"SCHEMA".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        SchemaNameClause schemaNameClause = parseSchemaNameClause();
        SchemaCharacterSetOrPath schemaCharacterSetOrPath = tryParseSchemaCharacterSetOrPath();
        List<SchemaElement> schemaElementList = null;
        while (true) {
            SchemaElement schemaElement = tryParseSchemaElement();
            if (schemaElement == null) {
                break;
            }
            if (schemaElementList == null) {
                schemaElementList = new ArrayList<>();
            }
            schemaElementList.add(schemaElement);
        }
        int endIndex = schemaNameClause.getEndIndex();
        if (schemaCharacterSetOrPath != null) {
            endIndex = schemaCharacterSetOrPath.getEndIndex();
        }
        if (schemaElementList != null) {
            endIndex = schemaElementList.get(schemaElementList.size() - 1).getEndIndex();
        }
        return new SchemaDefinition(
                token.getBeginIndex(),
                endIndex,
                schemaNameClause,
                schemaCharacterSetOrPath,
                schemaElementList == null ? null : new SchemaElementList(schemaElementList)
        );
    }

    //   <schema name>
    // | AUTHORIZATION <schema authorization identifier>
    // | <schema name> AUTHORIZATION <schema authorization identifier>
    private SchemaNameClause parseSchemaNameClause() {
        int beginIndex;
        int endIndex;
        SchemaName schemaName = tryParseSchemaName();
        Identifier schemaAuthorizationIdentifier;
        if (schemaName == null) {
            Token token = scan();
            if (!token.isAuthorization()) {
                throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a AUTHORIZATION.");
            }
            beginIndex = token.getBeginIndex();
            schemaAuthorizationIdentifier = parseIdentifier();
            endIndex = schemaAuthorizationIdentifier.getEndIndex();
        } else {
            beginIndex = schemaName.getBeginIndex();
            int markedIndex = mark();
            Token token = scan();
            if (token.isAuthorization()) {
                schemaAuthorizationIdentifier = parseIdentifier();
                endIndex = schemaAuthorizationIdentifier.getEndIndex();
            } else {
                reset(markedIndex);
                schemaAuthorizationIdentifier = null;
                endIndex = schemaName.getEndIndex();
            }
        }
        return new SchemaNameClause(beginIndex, endIndex, schemaName, schemaAuthorizationIdentifier);
    }

    private SchemaName parseSchemaName() {
        SchemaName schemaName = tryParseSchemaName();
        if (schemaName != null) {
            return schemaName;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to schema name.");
    }

    // [ <catalog name> <period> ] <unqualified schema name>
    private SchemaName tryParseSchemaName() {
        Identifier a = tryParseIdentifier();
        if (a == null) {
            return null;
        }
        int markedIndex = mark();
        Token token = scan();
        if (token.isPeriod()) {
            Identifier unqualifiedSchemaName = tryParseIdentifier();
            if (unqualifiedSchemaName == null) {
                Token token1 = scan();
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Cannot resolve to unqualified schema name.");
            }
            CatalogName catalogName = new CatalogName(a);
            return new SchemaName(catalogName, unqualifiedSchemaName);
        }
        reset(markedIndex);
        return new SchemaName(null, a);
    }

    //   <schema character set specification>
    // | <schema path specification>
    // | <schema character set specification> <schema path specification>
    // | <schema path specification> <schema character set specification>
    private SchemaCharacterSetOrPath tryParseSchemaCharacterSetOrPath() {
        int beginIndex;
        int endIndex;
        ShemaCharacterSetSpecification shemaCharacterSetSpecification
                = tryParseShemaCharacterSetSpecification();;
        PathSpecification schemaPathSpecification = null;
        if (shemaCharacterSetSpecification != null) {
            beginIndex = shemaCharacterSetSpecification.getBeginIndex();
            schemaPathSpecification = tryParsePathSpecification();
            if (schemaPathSpecification != null) {
                endIndex = schemaPathSpecification.getEndIndex();
            } else {
                endIndex = shemaCharacterSetSpecification.getEndIndex();
            }
        } else {
            schemaPathSpecification = tryParsePathSpecification();
            if (schemaPathSpecification != null) {
                beginIndex = schemaPathSpecification.getBeginIndex();
                shemaCharacterSetSpecification = tryParseShemaCharacterSetSpecification();
                if (shemaCharacterSetSpecification != null) {
                    endIndex = shemaCharacterSetSpecification.getEndIndex();
                } else {
                    endIndex = schemaPathSpecification.getEndIndex();
                }
            } else {
                return null;
            }
        }
        return new SchemaCharacterSetOrPath(
                beginIndex,
                endIndex,
                shemaCharacterSetSpecification,
                schemaPathSpecification
        );
    }

    // PATH <schema name list>
    private PathSpecification tryParsePathSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"PATH".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        SchemaName schemaName = parseSchemaName();
        List<SchemaName> schemaNameList = new ArrayList<>();
        schemaNameList.add(schemaName);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            SchemaName schemaName1 = parseSchemaName();
            schemaNameList.add(schemaName1);
        }
        return new PathSpecification(token.getBeginIndex(), new SchemaNameList(schemaNameList));
    }

    // DEFAULT CHARACTER SET <character set specification>
    private ShemaCharacterSetSpecification tryParseShemaCharacterSetSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDefault()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isCharacter()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a CHARACTER.");
        }
        Token token2 = scan();
        if (!token2.isSet()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a SET.");
        }
        CharacterSetName characterSetSpecification = parseCharacterSetName();
        return new ShemaCharacterSetSpecification(
                token.getBeginIndex(),
                characterSetSpecification
        );
    }

    //   <table definition>
    // | <view definition>
    // | <domain definition>
    // | <character set definition>
    // | <collation definition>
    // | <transliteration definition>
    // | <assertion definition>
    // | <trigger definition>
    // | <user-defined type definition>
    // | <user-defined cast definition>
    // | <user-defined ordering definition>
    // | <transform definition>
    // | <schema routine>
    // | <sequence generator definition>
    // | <grant statement>
    // | <role definition>
    private SchemaElement tryParseSchemaElement() {
        TableDefinition tableDefinition = tryParseTableDefinition();
        if (tableDefinition != null) {
            return tableDefinition;
        }
        ViewDefinition viewDefinition = tryParseViewDefinition();
        if (viewDefinition != null) {
            return viewDefinition;
        }
        DomainDefinition domainDefinition = tryParseDomainDefinition();
        if (domainDefinition != null) {
            return domainDefinition;
        }
        CharacterSetDefinition characterSetDefinition = tryParseCharacterSetDefinition();
        if (characterSetDefinition != null) {
            return characterSetDefinition;
        }
        CollationDefinition collationDefinition = tryParseCollationDefinition();
        if (collationDefinition != null) {
            return collationDefinition;
        }
        TransliterationDefinition transliterationDefinition = tryParseTranslationDefinition();
        if (transliterationDefinition != null) {
            return transliterationDefinition;
        }
        AssertionDefinition assertionDefinition = tryParseAssertionDefinition();
        if (assertionDefinition != null) {
            return assertionDefinition;
        }
        TriggerDefinition triggerDefinition = tryParseTriggerDefinition();
        if (triggerDefinition != null) {
            return triggerDefinition;
        }
        UserDefinedTypeDefinition userDefinedTypeDefinition
                = tryParseUserDefinedTypeDefinition();
        if (userDefinedTypeDefinition != null) {
            return userDefinedTypeDefinition;
        }
        UserDefinedCastDefinition userDefinedCastDefinition
                = tryParseUserDefinedCastDefinition();
        if (userDefinedCastDefinition != null) {
            return userDefinedCastDefinition;
        }
        UserDefinedOrderingDefinition userDefinedOrderingDefinition
                = tryParseUserDefinedOrderingDefinition();
        if (userDefinedOrderingDefinition != null) {
            return userDefinedOrderingDefinition;
        }
        TransformDefinition transformDefinition = tryParseTransformDefinition();
        if (transformDefinition != null) {
            return transformDefinition;
        }
        SchemaRoutine schemaRoutine = tryParseSchemaRoutine();
        if (schemaRoutine != null) {
            return schemaRoutine;
        }
        SequenceGeneratorDefinition sequenceGeneratorDefinition
                = tryParseSequenceGeneratorDefinition();
        if (sequenceGeneratorDefinition != null) {
            return sequenceGeneratorDefinition;
        }
        GrantStatement grantStatement = tryParseGrantStatement();
        if (grantStatement != null) {
            return grantStatement;
        }
        RoleDefinition roleDefinition = tryParseRoleDefinition();
        if (roleDefinition != null) {
            return roleDefinition;
        }
        return null;
    }

    // CREATE TYPE <user-defined type body>
    private UserDefinedTypeDefinition tryParseUserDefinedTypeDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCreate()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"TYPE".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        UserDefinedTypeBody userDefinedTypeBody = parseUserDefinedTypeBody();
        return new UserDefinedTypeDefinition(token.getBeginIndex(), userDefinedTypeBody);
    }

    // <schema-resolved user-defined type name>
    // [ <subtype clause> ]
    // [ AS <representation> ]
    // [ <user-defined type option list> ]
    // [ <method specification list> ]
    private UserDefinedTypeBody parseUserDefinedTypeBody() {
        SchemaQualifiedName schemaResolvedUserDefinedTypeName = parseSchemaQualifiedName();
        SubtypeClause subtypeClause = tryParseSubtypeClause();
        Representation representation = null;
        int markedIndex = mark();
        Token token = scan();
        if (token.isAs()) {
            representation = parseRepresentation();
        } else {
            reset(markedIndex);
        }
        UserDefinedTypeOptionList userDefinedTypeOptionList = tryParseUserDefinedTypeOptionList();
        MethodSpecificationList methodSpecificationList = tryParseMethodSpecificationList();
        return new UserDefinedTypeBody(
                schemaResolvedUserDefinedTypeName,
                subtypeClause,
                representation,
                userDefinedTypeOptionList,
                methodSpecificationList
        );
    }

    // <predefined type> | <member list>
    private Representation parseRepresentation() {
        PredefinedType predefinedType = tryParsePredefinedType();
        if (predefinedType != null) {
            return predefinedType;
        }
        MemberList memberList = tryParseMemberList();
        if (memberList != null) {
            return memberList;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to representation.");
    }

    // <left paren> <member> [ { <comma> <member> }... ] <right paren>
    private MemberList tryParseMemberList() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        List<AttributeDefinition> memberList = new ArrayList<>();
        while (true) {
            AttributeDefinition member = parseAttributeDefinition();
            memberList.add(member);
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
        }
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new MemberList(token.getBeginIndex(), token1.getEndIndex(), memberList);
    }

    private AttributeDefinition parseAttributeDefinition() {
        AttributeDefinition attributeDefinition = tryParseAttributeDefinition();
        if (attributeDefinition != null) {
            return attributeDefinition;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to attribute definition.");
    }

    // <attribute name> <data type> [ <reference scope check> ] [ <attribute default> ] [ <collate clause> ]
    private AttributeDefinition tryParseAttributeDefinition() {
        int markedIndex = mark();
        Identifier attributeName = tryParseIdentifier();
        if (attributeName == null) {
            return null;
        }
        DataType dataType = tryParseDataType();
        if (dataType == null) {
            reset(markedIndex);
            return null;
        }
        ReferenceScopeCheck referenceScopeCheck = tryParseReferenceScopeCheck();
        DefaultClause attributeDefault = tryParseDefaultClause();
        CollateClause collateClause = tryParseCollateClause();
        int endIndex = dataType.getEndIndex();
        if (referenceScopeCheck != null) {
            endIndex = referenceScopeCheck.getEndIndex();
        }
        if (attributeDefault != null) {
            endIndex = attributeDefault.getEndIndex();
        }
        if (collateClause != null) {
            endIndex = collateClause.getEndIndex();
        }
        return new AttributeDefinition(
                endIndex,
                attributeName,
                dataType,
                referenceScopeCheck,
                attributeDefault,
                collateClause
        );
    }

    // <user-defined type option> [ <user-defined type option> ... ]
    private UserDefinedTypeOptionList tryParseUserDefinedTypeOptionList() {
        UserDefinedTypeOption userDefinedTypeOption = tryParseUserDefinedTypeOption();
        if (userDefinedTypeOption == null) {
            return null;
        }
        List<UserDefinedTypeOption> userDefinedTypeOptionList = new ArrayList<>();
        userDefinedTypeOptionList.add(userDefinedTypeOption);
        while (true) {
            UserDefinedTypeOption userDefinedTypeOption1 = tryParseUserDefinedTypeOption();
            if (userDefinedTypeOption1 == null) {
                break;
            }
            userDefinedTypeOptionList.add(userDefinedTypeOption1);
        }
        return new UserDefinedTypeOptionList(userDefinedTypeOptionList);
    }

    //   <instantiable clause>
    // | <finality>
    // | <reference type specification>
    // | <ref cast option>
    // | <cast option>
    private UserDefinedTypeOption tryParseUserDefinedTypeOption() {
        InstantiableClause instantiableClause = tryParseInstantiableClause();
        if (instantiableClause != null) {
            return instantiableClause;
        }
        Finality finality = tryParseFinality();
        if (finality != null) {
            return finality;
        }
        ReferenceTypeSpecification referenceTypeSpecification = tryParseReferenceTypeSpecification();
        if (referenceTypeSpecification != null) {
            return referenceTypeSpecification;
        }
        RefCastOption refCastOption = tryParseRefCastOption();
        if (refCastOption != null) {
            return refCastOption;
        }
        CastOption castOption = tryParseCastOption();
        if (castOption != null) {
            return castOption;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to user-defined type option.");
    }

    // INSTANTIABLE | NOT INSTANTIABLE
    private InstantiableClause tryParseInstantiableClause() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isNot()) {
            Token token1 = scan();
            if (!token1.isRegularIdentifier() || !"INSTANTIABLE".equalsIgnoreCase(token1.getContent())) {
                reset(markedIndex);
                return null;
            }
            return new InstantiableClause(token.getBeginIndex(), token1.getEndIndex(), true);
        }
        reset(markedIndex);
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"INSTANTIABLE".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        return new InstantiableClause(token.getBeginIndex(), token1.getEndIndex(), false);
    }

    // FINAL | NOT FINAL
    private Finality tryParseFinality() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isNot()) {
            Token token1 = scan();
            if (!token1.isRegularIdentifier() || !"FINAL".equalsIgnoreCase(token1.getContent())) {
                reset(markedIndex);
                return null;
            }
            return new Finality(token.getBeginIndex(), token1.getEndIndex(), true);
        }
        reset(markedIndex);
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"FINAL".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        return new Finality(token.getBeginIndex(), token1.getEndIndex(), false);
    }

    //   <user-defined representation>
    // | <derived representation>
    // | <system-generated representation>
    private ReferenceTypeSpecification tryParseReferenceTypeSpecification() {
        UserDefinedRepresentation userDefinedRepresentation
                = tryParseUserDefinedRepresentation();
        if (userDefinedRepresentation != null) {
            return userDefinedRepresentation;
        }
        DerivedRepresentation derivedRepresentation
                = tryParseDerivedRepresentation();
        if (derivedRepresentation != null) {
            return derivedRepresentation;
        }
        SystemGeneratedRepresentation systemGeneratedRepresentation
                = tryParseSystemGeneratedRepresentation();
        if (systemGeneratedRepresentation != null) {
            return systemGeneratedRepresentation;
        }
        return null;
    }

    // REF USING <predefined type>
    private UserDefinedRepresentation tryParseUserDefinedRepresentation() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRef()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isUsing()) {
            reset(markedIndex);
            return null;
        }
        PredefinedType predefinedType = parsePredefinedType();
        return new UserDefinedRepresentation(token.getBeginIndex(), predefinedType);
    }

    // REF FROM <list of attributes>
    private DerivedRepresentation tryParseDerivedRepresentation() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRef()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isFrom()) {
            reset(markedIndex);
            return null;
        }
        ListOfAttributes listOfAttributes = parseListOfAttributes();
        return new DerivedRepresentation(token.getBeginIndex(), listOfAttributes);
    }

    // <left paren> <attribute name> [ { <comma> <attribute name> }...] <right paren>
    private ListOfAttributes parseListOfAttributes() {
        Token token = scan();
        if (!token.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a (.");
        }
        Identifier attributeName = parseIdentifier();
        List<Identifier> attributeNameList = new ArrayList<>();
        attributeNameList.add(attributeName);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            Identifier attributeName1 = parseIdentifier();
            attributeNameList.add(attributeName1);
        }
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new ListOfAttributes(
                token.getBeginIndex(),
                token1.getEndIndex(),
                attributeNameList
        );
    }

    // REF IS SYSTEM GENERATED
    private SystemGeneratedRepresentation tryParseSystemGeneratedRepresentation() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRef()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isIs()) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isSystem()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a SYSTEM.");
        }
        Token token3 = scan();
        if (!token3.isRegularIdentifier() || !"GENERATED".equalsIgnoreCase(token3.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a GENERATED.");
        }
        return new SystemGeneratedRepresentation(token.getBeginIndex(), token3.getEndIndex());
    }

    // [ <cast to ref> ] [ <cast to type> ]
    private RefCastOption tryParseRefCastOption() {
        CastToRef castToRef = tryParseCastToRef();
        CastToType castToType = tryParseCastToType();
        if (castToRef == null && castToType == null) {
            return null;
        }
        return new RefCastOption(castToRef, castToType);
    }

    // CAST <left paren> SOURCE AS REF <right paren> WITH <cast to ref identifier>
    private CastToRef tryParseCastToRef() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCast()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isRegularIdentifier() || !"SOURCE".equalsIgnoreCase(token2.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token3 = scan();
        if (!token3.isAs()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a AS.");
        }
        Token token4 = scan();
        if (!token4.isRef()) {
            reset(markedIndex);
            return null;
        }
        Token token5 = scan();
        if (!token5.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing a ).");
        }
        Token token6 = scan();
        if (!token6.isWith()) {
            throw new YuliaException(getSql(), getLineNumber(), token6.getBeginIndex(), "Missing a WITH.");
        }
        Identifier castToRefIdentifier = parseIdentifier();
        return new CastToRef(token.getBeginIndex(), castToRefIdentifier);
    }

    // CAST <left paren> REF AS SOURCE <right paren> WITH <cast to type identifier>
    private CastToType tryParseCastToType() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCast()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isRef()) {
            reset(markedIndex);
            return null;
        }
        Token token3 = scan();
        if (!token3.isAs()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a AS.");
        }
        Token token4 = scan();
        if (!token4.isRegularIdentifier() || !"SOURCE".equalsIgnoreCase(token4.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a SOURCE.");
        }
        Token token5 = scan();
        if (!token5.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing a ).");
        }
        Token token6 = scan();
        if (!token6.isWith()) {
            throw new YuliaException(getSql(), getLineNumber(), token6.getBeginIndex(), "Missing a WITH.");
        }
        Identifier castToTypeIdentifier = parseIdentifier();
        return new CastToType(token.getBeginIndex(), castToTypeIdentifier);
    }

    // [ <cast to distinct> ] [ <cast to source> ]
    private CastOption tryParseCastOption() {
        CastToDistinct castToDistinct = tryParseCastToDistinct();
        CastToSource castToSource = tryParseCastToSource();
        if (castToDistinct == null && castToSource == null) {
            return null;
        }
        return new CastOption(castToDistinct, castToSource);
    }

    // CAST <left paren> SOURCE AS DISTINCT <right paren>
    // WITH <cast to distinct identifier>
    private CastToDistinct tryParseCastToDistinct() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCast()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isRegularIdentifier() || !"SOURCE".equalsIgnoreCase(token2.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token3 = scan();
        if (!token3.isAs()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a AS.");
        }
        Token token4 = scan();
        if (!token4.isDistinct()) {
            reset(markedIndex);
            return null;
        }
        Token token5 = scan();
        if (!token5.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing a ).");
        }
        Token token6 = scan();
        if (!token6.isWith()) {
            throw new YuliaException(getSql(), getLineNumber(), token6.getBeginIndex(), "Missing a WITH.");
        }
        Identifier castToDistinctIdentifier = parseIdentifier();
        return new CastToDistinct(token.getBeginIndex(), castToDistinctIdentifier);
    }

    // CAST <left paren> DISTINCT AS SOURCE <right paren>
    // WITH <cast to source identifier>
    private CastToSource tryParseCastToSource() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCast()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isDistinct()) {
            reset(markedIndex);
            return null;
        }
        Token token3 = scan();
        if (!token3.isAs()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a AS.");
        }
        Token token4 = scan();
        if (!token4.isRegularIdentifier() || !"SOURCE".equalsIgnoreCase(token4.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a SOURCE.");
        }
        Token token5 = scan();
        if (!token5.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing a ).");
        }
        Token token6 = scan();
        if (!token6.isWith()) {
            throw new YuliaException(getSql(), getLineNumber(), token6.getBeginIndex(), "Missing a WITH.");
        }
        Identifier castToSourceIdentifier = parseIdentifier();
        return new CastToSource(token.getBeginIndex(), castToSourceIdentifier);
    }

    private MethodSpecificationList tryParseMethodSpecificationList() {
        MethodSpecification methodSpecification = tryParseMethodSpecification();
        if (methodSpecification == null) {
            return null;
        }
        List<MethodSpecification> methodSpecificationList = new ArrayList<>();
        methodSpecificationList.add(methodSpecification);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isComma()) {
                reset(markedIndex);
                break;
            }
            MethodSpecification methodSpecification1 = parseMethodSpecification();
            methodSpecificationList.add(methodSpecification1);
        }
        return new MethodSpecificationList(methodSpecificationList);
    }

    private MethodSpecification parseMethodSpecification() {
        MethodSpecification methodSpecification = tryParseMethodSpecification();
        if (methodSpecification != null) {
            return methodSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to method specification.");
    }

    // <original method specification> | <overriding method specification>
    private MethodSpecification tryParseMethodSpecification() {
        OriginalMethodSpecification originalMethodSpecification
                = tryParseOriginalMethodSpecification();
        if (originalMethodSpecification != null) {
            return originalMethodSpecification;
        }
        OverridingMethodSpecification overridingMethodSpecification
                = tryParseOverridingMethodSpecification();
        if (overridingMethodSpecification != null) {
            return overridingMethodSpecification;
        }
        return null;
    }

    private OriginalMethodSpecification parseOriginalMethodSpecification() {
        OriginalMethodSpecification originalMethodSpecification
                = tryParseOriginalMethodSpecification();
        if (originalMethodSpecification != null) {
            return originalMethodSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to original method specification.");
    }

    // <partial method specification> [ SELF AS RESULT ] [ SELF AS LOCATOR ] [ <method characteristics> ]
    private OriginalMethodSpecification tryParseOriginalMethodSpecification() {
        PartialMethodSpecification partialMethodSpecification = tryParsePartialMethodSpecification();
        if (partialMethodSpecification == null) {
            return null;
        }
        int endIndex = partialMethodSpecification.getEndIndex();
        boolean selfAsResult = false;
        boolean selfAsLocator = false;
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "SELF".equalsIgnoreCase(token.getContent())) {
            Token token1 = scan();
            if (!token1.isAs()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a AS.");
            }
            Token token2 = scan();
            if (token2.isRegularIdentifier() && "RESULT".equalsIgnoreCase(token2.getContent())) {
                selfAsResult = true;
            } else if (token2.isRegularIdentifier() && "LOCATOR".equalsIgnoreCase(token2.getContent())) {
                selfAsLocator = true;
            } else {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a RESULT or LOCATOR.");
            }
            endIndex = token2.getEndIndex();
        } else {
            reset(markedIndex);
        }
        MethodCharacteristics methodCharacteristics = tryParseMethodCharacteristics();
        if (methodCharacteristics != null) {
            endIndex = methodCharacteristics.getEndIndex();
        }
        return new OriginalMethodSpecification(
                endIndex,
                partialMethodSpecification,
                selfAsResult,
                selfAsLocator,
                methodCharacteristics
        );
    }

    private PartialMethodSpecification parsePartialMethodSpecification() {
        PartialMethodSpecification partialMethodSpecification = tryParsePartialMethodSpecification();
        if (partialMethodSpecification != null) {
            return partialMethodSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to partial method specification.");
    }

    // [ INSTANCE | STATIC | CONSTRUCTOR ] METHOD <method name>
    // <SQL parameter declaration list>
    // <returns clause>
    // [ SPECIFIC <specific method name> ]
    private PartialMethodSpecification tryParsePartialMethodSpecification() {
        boolean instance = false;
        boolean static_ = false;
        boolean constructor = false;
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "INSTANCE".equalsIgnoreCase(token.getContent())) {
            instance = true;
        } else if (token.isStatic()) {
            static_ = true;
        } else if (token.isRegularIdentifier() && "CONSTRUCTOR".equalsIgnoreCase(token.getContent())) {
            constructor = true;
        } else {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isMethod()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a METHOD.");
        }
        Identifier methodName = parseIdentifier();
        SqlParameterDeclarationList sqlParameterDeclarationList = parseSqlParameterDeclarationList();
        ReturnsClause returnsClause = parseReturnsClause();
        int endIndex = returnsClause.getEndIndex();
        SchemaQualifiedName specificMethodName = null;
        int markedIndex2 = mark();
        Token token2 = scan();
        if (token2.isSpecific()) {
            specificMethodName = parseSchemaQualifiedName();
            endIndex = specificMethodName.getEndIndex();
        } else {
            reset(markedIndex2);
        }
        return new PartialMethodSpecification(
                token.getBeginIndex(),
                endIndex,
                instance,
                static_,
                constructor,
                methodName,
                sqlParameterDeclarationList,
                returnsClause,
                specificMethodName
        );
    }

    private ReturnsClause parseReturnsClause() {
        ReturnsClause returnsClause = tryParseReturnsClause();
        if (returnsClause != null) {
            return returnsClause;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to returns clause.");
    }

    // RETURNS <returns type>
    private ReturnsClause tryParseReturnsClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isReturns()) {
            reset(markedIndex);
            return null;
        }
        ReturnsType returnsType = parseReturnsType();
        return new ReturnsClause(token.getBeginIndex(), returnsType);
    }

    //   <returns data type> [ <result cast> ]
    // | <returns table type>
    private ReturnsType parseReturnsType() {
        ReturnsDataTypeReturnsType returnsDataTypeReturnsType = tryParseReturnsDataTypeReturnsType();
        if (returnsDataTypeReturnsType != null) {
            return returnsDataTypeReturnsType;
        }
        ReturnsTableType returnsTableType = tryParseReturnsTableType();
        if (returnsTableType != null) {
            return returnsTableType;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to returns type.");
    }

    // <data type> [ <locator indication> ] [ <result cast> ]
    private ReturnsDataTypeReturnsType tryParseReturnsDataTypeReturnsType() {
        DataType dataType = tryParseDataType();
        if (dataType == null) {
            return null;
        }
        int endIndex = dataType.getEndIndex();
        LocatorIndication locatorIndication = tryParseLocatorIndication();
        if (locatorIndication != null) {
            endIndex = locatorIndication.getEndIndex();
        }
        ResultCast resultCast = tryParseResultCast();
        if (resultCast != null) {
            endIndex = resultCast.getEndIndex();
        }
        return new ReturnsDataTypeReturnsType(
                endIndex,
                dataType,
                locatorIndication,
                resultCast
        );
    }

    // CAST FROM <result cast from type>
    private ResultCast tryParseResultCast() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCast()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isFrom()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a FROM.");
        }
        ResultCastFromType resultCastFromType = parseResultCastFromType();
        return new ResultCast(
                token.getBeginIndex(),
                resultCastFromType
        );
    }

    // <data type> [ <locator indication> ]
    private ResultCastFromType parseResultCastFromType() {
        DataType dataType = parseDataType();
        LocatorIndication locatorIndication = tryParseLocatorIndication();
        return new ResultCastFromType(dataType, locatorIndication);
    }

    // TABLE <table function column list>
    private ReturnsTableType tryParseReturnsTableType() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isTable()) {
            reset(markedIndex);
            return null;
        }
        TableFunctionColumnList tableFunctionColumnList = parseTableFunctionColumnList();
        return new ReturnsTableType(token.getBeginIndex(), tableFunctionColumnList);
    }

    // <left paren>
    // <table function column list element> [ { <comma> <table function column list element> }... ]
    // <right paren>
    private TableFunctionColumnList parseTableFunctionColumnList() {
        Token token = scan();
        if (!token.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a (.");
        }
        TableFunctionColumnListElement tableFunctionColumnListElement = parseTableFunctionColumnListElement();
        List<TableFunctionColumnListElement> tableFunctionColumnList = new ArrayList<>();
        tableFunctionColumnList.add(tableFunctionColumnListElement);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            TableFunctionColumnListElement tableFunctionColumnListElement1 = parseTableFunctionColumnListElement();
            tableFunctionColumnList.add(tableFunctionColumnListElement1);
        }
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new TableFunctionColumnList(
                token.getBeginIndex(),
                token1.getEndIndex(),
                tableFunctionColumnList
        );
    }

    // <column name> <data type>
    private TableFunctionColumnListElement parseTableFunctionColumnListElement() {
        Identifier columnName = parseIdentifier();
        DataType dataType = parseDataType();
        return new TableFunctionColumnListElement(columnName, dataType);
    }

    // <method characteristic> ...
    private MethodCharacteristics tryParseMethodCharacteristics() {
        MethodCharacteristic methodCharacteristic = tryParseMethodCharacteristic();
        if (methodCharacteristic == null) {
            return null;
        }
        List<MethodCharacteristic> methodCharacteristicList = new ArrayList<>();
        methodCharacteristicList.add(methodCharacteristic);
        while (true) {
            MethodCharacteristic methodCharacteristic1 = tryParseMethodCharacteristic();
            if (methodCharacteristic1 == null) {
                break;
            }
            methodCharacteristicList.add(methodCharacteristic1);
        }
        return new MethodCharacteristics(methodCharacteristicList);
    }

    //   <language clause>
    // | <parameter style clause>
    // | <deterministic characteristic>
    // | <SQL-data access indication>
    // | <null-call clause>
    private MethodCharacteristic tryParseMethodCharacteristic() {
        LanguageClause languageClause = tryParseLanguageClause();
        if (languageClause != null) {
            return languageClause;
        }
        ParameterStyleClause parameterStyleClause = tryParseParameterStyleClause();
        if (parameterStyleClause != null) {
            return parameterStyleClause;
        }
        DeterministicCharacteristic deterministicCharacteristic = tryParseDeterministicCharacteristic();
        if (deterministicCharacteristic != null) {
            return deterministicCharacteristic;
        }
        SqlDataAccessIndication sqlDataAccessIndication = tryParseSqlDataAccessIndication();
        if (sqlDataAccessIndication != null) {
            return sqlDataAccessIndication;
        }
        NullCallClause nullCallClause = tryParseNullCallClause();
        if (nullCallClause != null) {
            return nullCallClause;
        }
        return null;
    }

    private OverridingMethodSpecification parseOverridingMethodSpecification() {
        OverridingMethodSpecification overridingMethodSpecification
                = tryParseOverridingMethodSpecification();
        if (overridingMethodSpecification != null) {
            return overridingMethodSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to overriding method specification.");
    }

    // OVERRIDING <partial method specification>
    private OverridingMethodSpecification tryParseOverridingMethodSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"OVERRIDING".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        PartialMethodSpecification partialMethodSpecification = parsePartialMethodSpecification();
        return new OverridingMethodSpecification(
                token.getBeginIndex(),
                partialMethodSpecification
        );
    }

    // UNDER <supertype name>
    private SubtypeClause tryParseSubtypeClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"UNDER".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName supertypeName = parseSchemaQualifiedName();
        return new SubtypeClause(token.getBeginIndex(), supertypeName);
    }

    // CREATE CAST <left paren> <source data type> AS <target data type> <right paren>
    // WITH <cast function> [ AS ASSIGNMENT ]
    private UserDefinedCastDefinition tryParseUserDefinedCastDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCreate()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isCast()) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a (.");
        }
        DataType sourceDataType = parseDataType();
        Token token3 = scan();
        if (!token3.isAs()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a AS.");
        }
        DataType targetDataType = parseDataType();
        Token token4 = scan();
        if (!token4.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a ).");
        }
        Token token5 = scan();
        if (!token5.isWith()) {
            throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing a WITH.");
        }
        SpecificRoutineDesignator castFunction = parseSpecificRoutineDesignator();
        int endIndex = castFunction.getEndIndex();
        int markedIndex6 = mark();
        Token token6 = scan();
        if (token6.isAs()) {
            Token token7 = scan();
            if (!token7.isRegularIdentifier() || !"ASSIGNMENT".equalsIgnoreCase(token7.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token7.getBeginIndex(), "Missing a ASSIGNMENT.");
            }
            endIndex = token7.getEndIndex();
        } else {
            reset(markedIndex6);
        }
        return new UserDefinedCastDefinition(
                token.getBeginIndex(),
                endIndex,
                sourceDataType,
                targetDataType,
                castFunction
        );
    }

    // CREATE ORDERING FOR <schema-resolved user-defined type name> <ordering form>
    private UserDefinedOrderingDefinition tryParseUserDefinedOrderingDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCreate()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"ORDERING".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isFor()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a FOR.");
        }
        SchemaQualifiedName schemaResolvedUserDefinedTypeName = parseSchemaQualifiedName();
        OrderingForm orderingForm = parseOrderingForm();
        return new UserDefinedOrderingDefinition(
                token.getBeginIndex(),
                schemaResolvedUserDefinedTypeName,
                orderingForm
        );
    }

    // <equals ordering form> | <full ordering form>
    private OrderingForm parseOrderingForm() {
        EqualsOrderingForm equalsOrderingForm = tryParseEqualsOrderingForm();
        if (equalsOrderingForm != null) {
            return equalsOrderingForm;
        }
        FullOrderingForm fullOrderingForm = tryParseFullOrderingForm();
        if (fullOrderingForm != null) {
            return fullOrderingForm;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to ordering form.");
    }

    // EQUALS ONLY BY <ordering category>
    private EqualsOrderingForm tryParseEqualsOrderingForm() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"EQUALS".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isOnly()) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isBy()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a BY.");
        }
        OrderingCategory orderingCategory = parseOrderingCategory();
        return new EqualsOrderingForm(token.getBeginIndex(), orderingCategory);
    }

    // <relative category> | <map category> | <state category>
    private OrderingCategory parseOrderingCategory() {
        RelativeCategory relativeCategory = tryParseRelativeCategory();
        if (relativeCategory != null) {
            return relativeCategory;
        }
        MapCategory mapCategory = tryParseMapCategory();
        if (mapCategory != null) {
            return mapCategory;
        }
        StateCategory stateCategory = tryParseStateCategory();
        if (stateCategory != null) {
            return stateCategory;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to ordering category.");
    }

    // RELATIVE WITH <relative function specification>
    private RelativeCategory tryParseRelativeCategory() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"RELATIVE".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isWith()) {
            reset(markedIndex);
            return null;
        }
        SpecificRoutineDesignator relativeFunctionSpecification = parseSpecificRoutineDesignator();
        return new RelativeCategory(
                token.getBeginIndex(),
                relativeFunctionSpecification
        );
    }

    // MAP WITH <map function specification>
    private MapCategory tryParseMapCategory() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"MAP".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isWith()) {
            reset(markedIndex);
            return null;
        }
        SpecificRoutineDesignator relativeFunctionSpecification = parseSpecificRoutineDesignator();
        return new MapCategory(
                token.getBeginIndex(),
                relativeFunctionSpecification
        );
    }

    // STATE [ <specific name> ]
    private StateCategory tryParseStateCategory() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"STATE".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        int endIndex = token.getEndIndex();
        SchemaQualifiedName specificName = tryParseSchemaQualifiedName();
        if (specificName != null) {
            endIndex = specificName.getEndIndex();
        }
        return new StateCategory(token.getBeginIndex(), endIndex, specificName);
    }

    // ORDER FULL BY <ordering category>
    private FullOrderingForm tryParseFullOrderingForm() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isOrder()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isFull()) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isBy()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a BY.");
        }
        OrderingCategory orderingCategory = parseOrderingCategory();
        return new FullOrderingForm(token.getBeginIndex(), orderingCategory);
    }

    // CREATE { TRANSFORM | TRANSFORMS } FOR <schema-resolved user-defined type name> <transform group> ...
    private TransformDefinition tryParseTransformDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCreate()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() && !"TRANSFORM".equalsIgnoreCase(token1.getContent()) && !"TRANSFORMS".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isFor()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a FOR.");
        }
        SchemaQualifiedName schemaResolvedUserDefinedTypeName = parseSchemaQualifiedName();
        TransformGroup transformGroup = parseTransformGroup();
        List<TransformGroup> transformGroupList = new ArrayList<>();
        transformGroupList.add(transformGroup);
        while (true) {
            TransformGroup transformGroup1 = tryParseTransformGroup();
            if (transformGroup1 == null) {
                break;
            }
            transformGroupList.add(transformGroup1);
        }
        return new TransformDefinition(
                token.getBeginIndex(),
                schemaResolvedUserDefinedTypeName,
                new TransformGroupList(transformGroupList)
        );
    }

    private TransformGroup parseTransformGroup() {
        TransformGroup transformGroup = tryParseTransformGroup();
        if (transformGroup != null) {
            return transformGroup;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to transform group.");
    }

    // <group name> <left paren> <transform element list> <right paren>
    private TransformGroup tryParseTransformGroup() {
        int markedIndex = mark();
        Identifier groupName = tryParseIdentifier();
        if (groupName == null) {
            return null;
        }
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        TransformElementList transformElementList = parseTransformElementList();
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new TransformGroup(token.getBeginIndex(), token1.getEndIndex(), transformElementList);
    }

    // <transform element> [ <comma> <transform element> ]
    private TransformElementList parseTransformElementList() {
        TransformElement transformElement = parseTransformElement();
        List<TransformElement> transformElementList = new ArrayList<>();
        transformElementList.add(transformElement);
        int markedIndex = mark();
        Token token = scan();
        if (token.isComma()) {
            TransformElement transformElement1 = parseTransformElement();
            transformElementList.add(transformElement1);
        } else {
            reset(markedIndex);
        }
        return new TransformElementList(transformElementList);
    }

    private TransformElement parseTransformElement() {
        TransformElement transformElement = tryParseTransformElement();
        if (transformElement != null) {
            return transformElement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to transform element.");
    }

    // <to sql> | <from sql>
    private TransformElement tryParseTransformElement() {
        ToSql toSql = tryParseToSql();
        if (toSql != null) {
            return toSql;
        }
        FromSql fromSql = tryParseFromSql();
        if (fromSql != null) {
            return fromSql;
        }
        return null;
    }

    // TO SQL WITH <to sql function>
    private ToSql tryParseToSql() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isTo()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isSql()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SQL.");
        }
        Token token2 = scan();
        if (!token2.isWith()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a WITH.");
        }
        SpecificRoutineDesignator toSqlFunction = parseSpecificRoutineDesignator();
        return new ToSql(token.getBeginIndex(), toSqlFunction);
    }

    // FROM SQL WITH <from sql function>
    private FromSql tryParseFromSql() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isFrom()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isSql()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SQL.");
        }
        Token token2 = scan();
        if (!token2.isWith()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a WITH.");
        }
        SpecificRoutineDesignator fromSqlFunction = parseSpecificRoutineDesignator();
        return new FromSql(token.getBeginIndex(), fromSqlFunction);
    }

    // <schema procedure> | <schema function>
    private SchemaRoutine tryParseSchemaRoutine() {
        SchemaProcedure schemaProcedure = tryParseSchemaProcedure();
        if (schemaProcedure != null) {
            return schemaProcedure;
        }
        SchemaFunction schemaFunction = tryParseSchemaFunction();
        if (schemaFunction != null) {
            return schemaFunction;
        }
        return null;
    }

    // CREATE SEQUENCE <sequence generator name> [ <sequence generator options> ]
    private SequenceGeneratorDefinition tryParseSequenceGeneratorDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCreate()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"SEQUENCE".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName sequenceGeneratorName = parseSchemaQualifiedName();
        int endIndex = sequenceGeneratorName.getEndIndex();
        SequenceGeneratorOptions sequenceGeneratorOptions = tryParseSequenceGeneratorOptions();
        if (sequenceGeneratorOptions != null) {
            endIndex = sequenceGeneratorOptions.getEndIndex();
        }
        return new SequenceGeneratorDefinition(
                token.getBeginIndex(),
                endIndex,
                sequenceGeneratorName,
                sequenceGeneratorOptions
        );
    }

    // <sequence generator option> ...
    private SequenceGeneratorOptions tryParseSequenceGeneratorOptions() {
        SequenceGeneratorOption sequenceGeneratorOption = tryParseSequenceGeneratorOption();
        if (sequenceGeneratorOption == null) {
            return null;
        }
        List<SequenceGeneratorOption> sequenceGeneratorOptionList = new ArrayList<>();
        sequenceGeneratorOptionList.add(sequenceGeneratorOption);
        while (true) {
            SequenceGeneratorOption sequenceGeneratorOption1 = tryParseSequenceGeneratorOption();
            if (sequenceGeneratorOption1 == null) {
                break;
            }
            sequenceGeneratorOptionList.add(sequenceGeneratorOption1);
        }
        return new SequenceGeneratorOptions(sequenceGeneratorOptionList);
    }

    // <sequence generator data type option> | <common sequence generator options>
    private SequenceGeneratorOption tryParseSequenceGeneratorOption() {
        SequenceGeneratorDataTypeOption sequenceGeneratorDataTypeOption
                = tryParseSequenceGeneratorDataTypeOption();
        if (sequenceGeneratorDataTypeOption != null) {
            return sequenceGeneratorDataTypeOption;
        }
        CommonSequenceGeneratorOptions commonSequenceGeneratorOptions
                = tryParseCommonSequenceGeneratorOptions();
        if (commonSequenceGeneratorOptions != null) {
            return commonSequenceGeneratorOptions;
        }
        return null;
    }

    // AS <data type>
    private SequenceGeneratorDataTypeOption tryParseSequenceGeneratorDataTypeOption() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAs()) {
            reset(markedIndex);
            return null;
        }
        DataType dataType = parseDataType();
        return new SequenceGeneratorDataTypeOption(token.getBeginIndex(), dataType);
    }

    // <common sequence generator option> ...
    private CommonSequenceGeneratorOptions tryParseCommonSequenceGeneratorOptions() {
        CommonSequenceGeneratorOption commonSequenceGeneratorOption
                = tryParseCommonSequenceGeneratorOption();
        if (commonSequenceGeneratorOption == null) {
            return null;
        }
        List<CommonSequenceGeneratorOption> commonSequenceGeneratorOptionList
                = new ArrayList<>();
        commonSequenceGeneratorOptionList.add(commonSequenceGeneratorOption);
        while (true) {
            CommonSequenceGeneratorOption commonSequenceGeneratorOption1
                    = tryParseCommonSequenceGeneratorOption();
            if (commonSequenceGeneratorOption1 == null) {
                break;
            }
            commonSequenceGeneratorOptionList.add(commonSequenceGeneratorOption1);
        }
        return new CommonSequenceGeneratorOptions(commonSequenceGeneratorOptionList);
    }

    // CREATE ROLE <role name> [ WITH ADMIN <grantor> ]
    private RoleDefinition tryParseRoleDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCreate()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"ROLE".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        Identifier roleName = parseIdentifier();
        Grantor grantor = null;
        int markedIndex1 = mark();
        Token token2 = scan();
        if (token2.isWith()) {
            Token token3 = scan();
            if (!token3.isRegularIdentifier() || !"ADMIN".equalsIgnoreCase(token3.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ADMIN.");
            }
            grantor = parseGrantor();
        } else {
            reset(markedIndex1);
        }
        return new RoleDefinition(
                token.getBeginIndex(),
                roleName,
                grantor
        );
    }

    private DomainDefinition tryParseDomainDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCreate()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"DOMAIN".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName domainName = parseSchemaQualifiedName();
        int markedIndex2 = mark();
        Token token2 = scan();
        if (!token2.isAs()) {
            reset(markedIndex2);
        }
        DataType dataType = parseDataType();
        int endIndex = dataType.getEndIndex();
        DefaultClause defaultClause = tryParseDefaultClause();
        if (defaultClause != null) {
            endIndex = defaultClause.getEndIndex();
        }
        List<DomainConstraint> domainConstraintList = null;
        while (true) {
            DomainConstraint domainConstraint = tryParseDomainConstraint();
            if (domainConstraint == null) {
                break;
            }
            if (domainConstraintList == null) {
                domainConstraintList = new ArrayList<>();
            }
            domainConstraintList.add(domainConstraint);
            endIndex = domainConstraint.getEndIndex();
        }
        CollateClause collateClause = tryParseCollateClause();
        if (collateClause != null) {
            endIndex = collateClause.getEndIndex();
        }
        return new DomainDefinition(
                token.getBeginIndex(),
                endIndex,
                domainName,
                dataType,
                defaultClause,
                domainConstraintList == null ? null : new DomainConstraintList(domainConstraintList),
                collateClause);
    }

    private AssertionDefinition tryParseAssertionDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCreate()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"ASSERTION".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName constraintName = parseSchemaQualifiedName();
        Token token2 = scan();
        if (!token2.isCheck()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a CHECK.");
        }
        Token token3 = scan();
        if (!token3.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a (.");
        }
        BooleanValueExpression searchCondition = parseBooleanValueExpression();
        Token token4 = scan();
        if (!token4.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a ).");
        }
        int endIndex = token4.getEndIndex();
        ConstraintCharacteristics constraintCharacteristics = tryParseConstraintCharacteristics();
        if (constraintCharacteristics != null) {
            endIndex = constraintCharacteristics.getEndIndex();
        }
        return new AssertionDefinition(
                token.getBeginIndex(),
                endIndex,
                constraintName,
                searchCondition,
                constraintCharacteristics);
    }

    private ViewDefinition parseViewDefinition() {
        ViewDefinition viewDefinition = tryParseViewDefinition();
        if (viewDefinition != null) {
            return viewDefinition;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to view definition.");
    }

    // CREATE [ RECURSIVE ] VIEW <table name> <view specification> AS <query expression> [ WITH [ <levels clause> ] CHECK OPTION ]
    private ViewDefinition tryParseViewDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCreate()) {
            reset(markedIndex);
            return null;
        }
        boolean recursive;
        Token token1 = scan();
        if (token1.isRecursive()) {
            token1 = scan();
            recursive = true;
        } else {
            recursive = false;
        }
        if (!token1.isRegularIdentifier() || !"VIEW".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        LocalOrSchemaQualifiedName tableName = parseLocalOrSchemaQualifiedName();
        ViewSpecification viewSpecification = parseViewSpecification();
        Token token2 = scan();
        if (!token2.isAs()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a (.");
        }
        QueryExpression queryExpression = parseQueryExpression();
        int endIndex = queryExpression.getEndIndex();
        boolean withCascadedCheckOption = false;
        boolean withLocalCheckOption = false;
        boolean withCheckOption = false;
        int markedIndex3 = mark();
        Token token3 = scan();
        if (token3.isWith()) {
            int markedIndex4 = mark();
            Token token4 = scan();
            if (token4.isCascaded()) {
                withCascadedCheckOption = true;
            } else if (token4.isLocal()) {
                withLocalCheckOption = true;
            } else {
                reset(markedIndex4);
                withCheckOption = true;
            }
            Token token5 = scan();
            if (!token5.isCheck()) {
                throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing a CHECK");
            }
            Token token6 = scan();
            if (!token6.isRegularIdentifier() || !"OPTION".equalsIgnoreCase(token6.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token6.getBeginIndex(), "Missing a OPTION.");
            }
            endIndex = token6.getEndIndex();
        } else {
            reset(markedIndex3);
        }
        return new ViewDefinition(
                token.getBeginIndex(),
                endIndex,
                recursive,
                tableName,
                viewSpecification,
                queryExpression,
                withCascadedCheckOption,
                withLocalCheckOption,
                withCheckOption);
    }

    private ViewSpecification parseViewSpecification() {
        RegularViewSpecification regularViewSpecification = tryParseRegularViewSpecification();
        if (regularViewSpecification != null) {
            return regularViewSpecification;
        }
        ReferenceableViewSpecification referenceableViewSpecification = tryParseReferenceableViewSpecification();
        if (referenceableViewSpecification != null) {
            return referenceableViewSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to view specification.");
    }

    private RegularViewSpecification tryParseRegularViewSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        IdentifierList viewColumnList = parseIdentifierList();
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new RegularViewSpecification(token.getBeginIndex(), token1.getEndIndex(), viewColumnList);
    }

    // OF <path-resolved user-defined type name> [ <subview clause> ] [ <view element list> ]
    private ReferenceableViewSpecification tryParseReferenceableViewSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isOf()) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName pathResolvedUserDefinedTypeName = parseSchemaQualifiedName();
        SubviewClause subviewClause = tryParseSubviewClause();
        ViewElementList viewElementList = tryParseViewElementList();
        int endIndex = pathResolvedUserDefinedTypeName.getEndIndex();
        if (subviewClause != null) {
            endIndex = subviewClause.getEndIndex();
        }
        if (viewElementList != null) {
            endIndex = viewElementList.getEndIndex();
        }
        return new ReferenceableViewSpecification(
                token.getBeginIndex(),
                endIndex,
                pathResolvedUserDefinedTypeName,
                subviewClause,
                viewElementList
        );
    }

    // UNDER <table name>
    private SubviewClause tryParseSubviewClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"UNDER".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        LocalOrSchemaQualifiedName tableName = parseLocalOrSchemaQualifiedName();
        return new SubviewClause(token.getBeginIndex(), tableName);
    }

    // <left paren> <view element> [ { <comma> <view element> }... ] <right paren>
    private ViewElementList tryParseViewElementList() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        ViewElement viewElement = parseViewElement();
        List<ViewElement> viewElementList = new ArrayList<>();
        viewElementList.add(viewElement);
        while (true) {
            ViewElement viewElement1 = tryParseViewElement();
            if (viewElement1 == null) {
                break;
            }
            viewElementList.add(viewElement1);
        }
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new ViewElementList(token.getBeginIndex(), token1.getEndIndex(), viewElementList);
    }

    private ViewElement parseViewElement() {
        ViewElement viewElement = tryParseViewElement();
        if (viewElement != null) {
            return viewElement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to view element.");
    }

    // <self-referencing column specification> | <view column option>
    private ViewElement tryParseViewElement() {
        SelfReferencingColumnSpecification selfReferencingColumnSpecification
                = tryParseSelfReferencingColumnSpecification();
        if (selfReferencingColumnSpecification != null) {
            return selfReferencingColumnSpecification;
        }
        return tryParseViewColumnOption();
    }

    // REF IS <self-referencing column name> <reference generation>
    private SelfReferencingColumnSpecification tryParseSelfReferencingColumnSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRef()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isIs()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a IS.");
        }
        Identifier selfReferencingColumnName = parseIdentifier();
        ReferenceGeneration referenceGeneration = parseReferenceGeneration();
        return new SelfReferencingColumnSpecification(
                token.getBeginIndex(),
                selfReferencingColumnName,
                referenceGeneration);
    }

    // SYSTEM GENERATED | USER GENERATED | DERIVED
    private ReferenceGeneration parseReferenceGeneration() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isSystem()) {
            Token token1 = scan();
            if (!token1.isRegularIdentifier() || !"GENERATED".equalsIgnoreCase(token1.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a GENERATED.");
            }
            return new ReferenceGeneration(
                    token.getBeginIndex(),
                    token1.getEndIndex(),
                    true,
                    false,
                    false
            );
        }
        if (token.isUser()) {
            Token token1 = scan();
            if (!token1.isRegularIdentifier() || !"GENERATED".equalsIgnoreCase(token1.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a GENERATED.");
            }
            return new ReferenceGeneration(
                    token.getBeginIndex(),
                    token1.getEndIndex(),
                    false,
                    true,
                    false
            );
        }
        if (token.isRegularIdentifier() && "DERIVED".equalsIgnoreCase(token.getContent())) {
            return new ReferenceGeneration(
                    token.getBeginIndex(),
                    token.getEndIndex(),
                    false,
                    false,
                    true
            );
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to reference generation.");
    }

    // <column name> WITH OPTIONS <scope clause>
    private ViewColumnOption tryParseViewColumnOption() {
        Identifier columnName = tryParseIdentifier();
        if (columnName == null) {
            return null;
        }
        Token token = scan();
        if (!token.isWith()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a WITH.");
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"OPTIONS".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a OPTIONS.");
        }
        ScopeClause scopeClause = parseScopeClause();
        return new ViewColumnOption(columnName, scopeClause);
    }

    private CharacterSetDefinition parseCharacterSetDefinition() {
        CharacterSetDefinition characterSetDefinition = tryParseCharacterSetDefinition();
        if (characterSetDefinition != null) {
            return characterSetDefinition;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to character set definition.");
    }

    private SchemaFunction parseSchemaFunction() {
        SchemaFunction schemaFunction = tryParseSchemaFunction();
        if (schemaFunction != null) {
            return schemaFunction;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to schema function.");
    }

    // CREATE { <function specification> | <method specification designator> } <routine body>
    private SchemaFunction tryParseSchemaFunction() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCreate()) {
            reset(markedIndex);
            return null;
        }
        FunctionSpecification functionSpecification = tryParseFunctionSpecification();
        MethodSpecificationDesignator methodSpecificationDesignator = null;
        if (functionSpecification == null) {
            methodSpecificationDesignator = tryParseMethodSpecificationDesignator();
            if (methodSpecificationDesignator == null) {
                reset(markedIndex);
                return null;
            }
        }
        RoutineBody routineBody = parseRoutineBody();
        return new SchemaFunction(token.getBeginIndex(), functionSpecification, methodSpecificationDesignator, routineBody);
    }

    // <routine body>
    // ::= <SQL routine spec>
    //   | <external body reference>
    private RoutineBody parseRoutineBody() {
        SqlRoutineSpec sqlRoutineSpec = tryParseSqlRoutineSpec();
        if (sqlRoutineSpec != null) {
            return sqlRoutineSpec;
        }
        ExternalBodyReference externalBodyReference = tryParseExternalBodyReference();
        if (externalBodyReference != null) {
            return externalBodyReference;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to routine body.");
    }

    // <SQL routine spec> ::= [ <rights clause> ] <SQL routine body>
    private SqlRoutineSpec tryParseSqlRoutineSpec() {
        RightsClause rightsClause = tryParseRightsClause();
        SqlExecutableStatement sqlRoutineBody = tryParseSqlExecutableStatement();
        if (sqlRoutineBody == null) {
            if (rightsClause == null) {
                return null;
            }
            Token token = scan();
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a SQL routine body.");
        }
        return new SqlRoutineSpec(rightsClause, sqlRoutineBody);
    }

    // <rights clause> ::= SQL SECURITY INVOKER | SQL SECURITY DEFINER
    private RightsClause tryParseRightsClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSql()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"SECURITY".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SECURITY.");
        }
        Token token2 = scan();
        if (token2.isRegularIdentifier()) {
            if ("INVOKER".equalsIgnoreCase(token2.getContent())) {
                return new RightsClause(
                        token.getBeginIndex(),
                        token2.getEndIndex(),
                        true,
                        false
                );
            }
            if ("DEFINER".equalsIgnoreCase(token2.getContent())) {
                return new RightsClause(
                        token.getBeginIndex(),
                        token2.getEndIndex(),
                        false,
                        true
                );
            }
        }
        throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a INVOKER or DEFINER.");
    }

    // <external body reference>
    // ::=
    // EXTERNAL
    // [ NAME <external routine name> ]
    // [ <parameter style clause> ]
    // [ <transform group specification> ]
    // [ <external security clause> ]
    private ExternalBodyReference tryParseExternalBodyReference() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isExternal()) {
            reset(markedIndex);
            return null;
        }
        int endIndex = token.getEndIndex();
        ExternalRoutineName externalRoutineName;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isRegularIdentifier() && "NAME".equalsIgnoreCase(token1.getContent())) {
            externalRoutineName = parseExternalRoutineName();
            endIndex = externalRoutineName.getEndIndex();
        } else {
            externalRoutineName = null;
            reset(markedIndex1);
        }
        ParameterStyleClause parameterStyleClause = tryParseParameterStyleClause();
        if (parameterStyleClause != null) {
            endIndex = parameterStyleClause.getEndIndex();
        }
        TransformGroupSpecification transformGroupSpecification
                = tryParseTransformGroupSpecification();
        if (transformGroupSpecification != null) {
            endIndex = transformGroupSpecification.getEndIndex();
        }
        ExternalSecurityClause externalSecurityClause = tryParseExternalSecurityClause();
        if (externalSecurityClause != null) {
            endIndex = externalSecurityClause.getEndIndex();
        }
        return new ExternalBodyReference(
                token.getBeginIndex(),
                endIndex,
                externalRoutineName,
                parameterStyleClause,
                transformGroupSpecification,
                externalSecurityClause
        );
    }

    // <transform group specification>
    // ::= TRANSFORM GROUP { <single group specification> | <multiple group specification> }
    private TransformGroupSpecification tryParseTransformGroupSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"TRANSFORM".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isGroup()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a GROUP.");
        }
        Identifier groupName = parseIdentifier();
        int markedIndex2 = mark();
        Token token2 = scan();
        if (!token2.isFor()) {
            reset(markedIndex2);
            return new TransformGroupSpecification(token.getBeginIndex(), groupName);
        }
        Token token3 = scan();
        if (!token3.isRegularIdentifier() || !"TYPE".equalsIgnoreCase(token3.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a TYPE.");
        }
        SchemaQualifiedName pathResolvedUserDefinedTypeName = parseSchemaQualifiedName();
        GroupSpecification groupSpecification = new GroupSpecification(
                groupName,
                pathResolvedUserDefinedTypeName
        );
        List<GroupSpecification> groupSpecificationList = new ArrayList<>();
        groupSpecificationList.add(groupSpecification);
        while (true) {
            int markedIndex4 = mark();
            Token token4 = scan();
            if (!token4.isComma()) {
                reset(markedIndex4);
                break;
            }
            Identifier groupName1 = parseIdentifier();
            Token token5 = scan();
            if (!token5.isFor()) {
                throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing a FOR.");
            }
            Token token6 = scan();
            if (!token6.isRegularIdentifier() || !"TYPE".equalsIgnoreCase(token6.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token6.getBeginIndex(), "Missing a TYPE.");
            }
            SchemaQualifiedName pathResolvedUserDefinedTypeName1 = parseSchemaQualifiedName();
            GroupSpecification groupSpecification1 = new GroupSpecification(
                    groupName1,
                    pathResolvedUserDefinedTypeName1
            );
            groupSpecificationList.add(groupSpecification1);
        }
        MultipleGroupSpecification multipleGroupSpecification
                = new MultipleGroupSpecification(groupSpecificationList);
        return new TransformGroupSpecification(token.getBeginIndex(), multipleGroupSpecification);
    }

    // <external security clause>
    // ::= EXTERNAL SECURITY DEFINER
    //   | EXTERNAL SECURITY INVOKER
    //   | EXTERNAL SECURITY IMPLEMENTATION DEFINED
    private ExternalSecurityClause tryParseExternalSecurityClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isExternal()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"SECURITY".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SECURITY.");
        }
        Token token2 = scan();
        if (token2.isRegularIdentifier()) {
            if ("DEFINER".equalsIgnoreCase(token2.getContent())) {
                return new ExternalSecurityClause(
                        token.getBeginIndex(),
                        token2.getEndIndex(),
                        true,
                        false,
                        false
                );
            }
            if ("INVOKER".equalsIgnoreCase(token2.getContent())) {
                return new ExternalSecurityClause(
                        token.getBeginIndex(),
                        token2.getEndIndex(),
                        false,
                        true,
                        false
                );
            }
            if ("IMPLEMENTATION".equalsIgnoreCase(token2.getContent())) {
                Token token3 = scan();
                if (token3.isRegularIdentifier() && "DEFINED".equalsIgnoreCase(token3.getContent())) {
                    return new ExternalSecurityClause(
                            token.getBeginIndex(),
                            token3.getEndIndex(),
                            false,
                            false,
                            true
                    );
                }
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a DEFINED.");
            }
        }
        throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a INVOKER, DEFINER or IMPLEMENTATION.");
    }

    // FUNCTION
    // <schema qualified routine name>
    // <SQL parameter declaration list>
    // <returns clause>
    // <routine characteristics>
    // [ <dispatch clause> ]
    private FunctionSpecification tryParseFunctionSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isFunction()) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName schemaQualifiedRoutineName = parseSchemaQualifiedName();
        SqlParameterDeclarationList sqlParameterDeclarationList = parseSqlParameterDeclarationList();
        ReturnsClause returnsClause = parseReturnsClause();
        RoutineCharacteristics routineCharacteristics = parseRoutineCharacteristics();
        DispatchClause dispatchClause = tryParseDispatchClause();
        return new FunctionSpecification(
                token.getBeginIndex(),
                schemaQualifiedRoutineName,
                sqlParameterDeclarationList,
                returnsClause,
                routineCharacteristics,
                dispatchClause
        );
    }

    // STATIC DISPATCH
    private DispatchClause tryParseDispatchClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isStatic()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"DISPATCH".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a DISPATCH.");
        }
        return new DispatchClause(token.getBeginIndex(), token1.getEndIndex());
    }

    //   SPECIFIC METHOD <specific method name>
    // | [ INSTANCE | STATIC | CONSTRUCTOR ] METHOD <method name> <SQL parameter declaration list>
    //   [ <returns clause> ] FOR <schema-resolved user-defined type name>
    private MethodSpecificationDesignator tryParseMethodSpecificationDesignator() {
        MethodSpecificationDesignatorSubtype1 methodSpecificationDesignatorSubtype1
                = tryParseMethodSpecificationDesignatorSubtype1();
        if (methodSpecificationDesignatorSubtype1 != null) {
            return methodSpecificationDesignatorSubtype1;
        }
        MethodSpecificationDesignatorSubtype2 methodSpecificationDesignatorSubtype2
                = tryParseMethodSpecificationDesignatorSubtype2();
        if (methodSpecificationDesignatorSubtype2 != null) {
            return methodSpecificationDesignatorSubtype2;
        }
        return null;
    }

    // SPECIFIC METHOD <specific method name>
    private MethodSpecificationDesignatorSubtype1 tryParseMethodSpecificationDesignatorSubtype1() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSpecific()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isMethod()) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName specificMethodName = parseSchemaQualifiedName();
        return new MethodSpecificationDesignatorSubtype1(token.getBeginIndex(), specificMethodName);
    }

    // [ INSTANCE | STATIC | CONSTRUCTOR ] METHOD <method name>
    // <SQL parameter declaration list>
    // [ <returns clause> ]
    // FOR <schema-resolved user-defined type name>
    private MethodSpecificationDesignatorSubtype2 tryParseMethodSpecificationDesignatorSubtype2() {
        boolean instance = false;
        boolean static_ = false;
        boolean constructor = false;
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "INSTANCE".equalsIgnoreCase(token.getContent())) {
            instance = true;
            token = scan();
        } else if (token.isStatic()) {
            static_ = true;
            token = scan();
        } else if (token.isRegularIdentifier() && "".equalsIgnoreCase(token.getContent())) {
            constructor = true;
            token = scan();
        }
        if (!token.isMethod()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a METHOD.");
        }
        Identifier methodName = parseIdentifier();
        SqlParameterDeclarationList sqlParameterDeclarationList = parseSqlParameterDeclarationList();
        ReturnsClause returnsClause = tryParseReturnsClause();
        Token token1 = scan();
        if (!token1.isFor()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a FOR.");
        }
        SchemaQualifiedName schemaResolvedUserDefinedTypeName = parseSchemaQualifiedName();
        return new MethodSpecificationDesignatorSubtype2(
                token.getBeginIndex(),
                instance,
                static_,
                constructor,
                methodName,
                sqlParameterDeclarationList,
                returnsClause,
                schemaResolvedUserDefinedTypeName
        );
    }

    private SchemaProcedure parseSchemaProcedure() {
        SchemaProcedure schemaProcedure = tryParseSchemaProcedure();
        if (schemaProcedure != null) {
            return schemaProcedure;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to schema procedure.");
    }

    // CREATE PROCEDURE <schema qualified routine name> <SQL parameter declaration list> <routine characteristics> <routine body>
    private SchemaProcedure tryParseSchemaProcedure() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCreate()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isProcedure()) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName schemaQualifiedRoutineName = parseSchemaQualifiedName();
        SqlParameterDeclarationList sqlParameterDeclarationList = parseSqlParameterDeclarationList();
        RoutineCharacteristics routineCharacteristics = parseRoutineCharacteristics();
        RoutineBody routineBody = parseRoutineBody();
        return new SchemaProcedure(
                token.getBeginIndex(),
                schemaQualifiedRoutineName,
                sqlParameterDeclarationList,
                routineCharacteristics,
                routineBody
        );
    }

    private SqlParameterDeclarationList parseSqlParameterDeclarationList() {
        SqlParameterDeclarationList sqlParameterDeclarationList
                = tryParseSqlParameterDeclarationList();
        if (sqlParameterDeclarationList != null) {
            return sqlParameterDeclarationList;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to SQL parameter declaration list.");
    }

    // <left paren> [ <SQL parameter declaration> [ { <comma> <SQL parameter declaration> }... ] ] <right paren>
    private SqlParameterDeclarationList tryParseSqlParameterDeclarationList() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        List<SqlParameterDeclaration> sqlParameterDeclarationList = null;
        SqlParameterDeclaration sqlParameterDeclaration = tryParseSqlParameterDeclaration();
        if (sqlParameterDeclaration != null) {
            sqlParameterDeclarationList = new ArrayList<>();
            sqlParameterDeclarationList.add(sqlParameterDeclaration);
            while (true) {
                int markedIndex1 = mark();
                Token token1 = scan();
                if (!token1.isComma()) {
                    reset(markedIndex1);
                    break;
                }
                SqlParameterDeclaration sqlParameterDeclaration1 = tryParseSqlParameterDeclaration();
                sqlParameterDeclarationList.add(sqlParameterDeclaration1);
            }
        }
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new SqlParameterDeclarationList(token.getBeginIndex(), token1.getEndIndex(), sqlParameterDeclarationList);
    }

    // [ <parameter mode> ] [ <SQL parameter name> ] <parameter type> [ RESULT ]
    private SqlParameterDeclaration tryParseSqlParameterDeclaration() {
        ParameterMode parameterMode = tryParseParameterMode();
        Identifier sqlParameterName = tryParseIdentifier();
        ParameterType parameterType = parseParameterType();
        int endIndex = parameterType.getEndIndex();
        boolean result = false;
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "RESULT".equalsIgnoreCase(token.getContent())) {
            result = true;
            endIndex = token.getEndIndex();
        } else {
            reset(markedIndex);
        }
        return new SqlParameterDeclaration(
                token.getBeginIndex(),
                endIndex,
                parameterMode,
                sqlParameterName,
                parameterType,
                result
        );
    }

    // IN | OUT | INOUT
    private ParameterMode tryParseParameterMode() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isIn()) {
            return new ParameterMode(token.getBeginIndex(), token.getEndIndex(), true, false, false);
        }
        if (token.isOut()) {
            return new ParameterMode(token.getBeginIndex(), token.getEndIndex(), false, true, false);
        }
        if (token.isInout()) {
            return new ParameterMode(token.getBeginIndex(), token.getEndIndex(), false, false, true);
        }
        reset(markedIndex);
        return null;
    }

    // <data type> [ <locator indication> ]
    private ParameterType parseParameterType() {
        DataType dataType = parseDataType();
        LocatorIndication locatorIndication = tryParseLocatorIndication();
        return new ParameterType(dataType, locatorIndication);
    }

    // AS LOCATOR
    private LocatorIndication tryParseLocatorIndication() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAs()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"LOCATOR".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a LOCATOR.");
        }
        return new LocatorIndication(token.getBeginIndex(), token1.getEndIndex());
    }

    private RoutineCharacteristics parseRoutineCharacteristics() {
        List<RoutineCharacteristic> routineCharacteristicList = null;
        while (true) {
            RoutineCharacteristic routineCharacteristic = tryParseRoutineCharacteristic();
            if (routineCharacteristic == null) {
                break;
            }
            if (routineCharacteristicList == null) {
                routineCharacteristicList = new ArrayList<>();
            }
            routineCharacteristicList.add(routineCharacteristic);
        }
        if (routineCharacteristicList == null) {
            return null;
        }
        return new RoutineCharacteristics(routineCharacteristicList);
    }

    //   <language clause>
    // | <parameter style clause>
    // | SPECIFIC <specific name>
    // | <deterministic characteristic>
    // | <SQL-data access indication>
    // | <null-call clause>
    // | <dynamic result sets characteristic>
    // | <savepoint level indication>
    private RoutineCharacteristic tryParseRoutineCharacteristic() {
        LanguageClause languageClause = tryParseLanguageClause();
        if (languageClause != null) {
            return languageClause;
        }
        ParameterStyleClause parameterStyleClause = tryParseParameterStyleClause();
        if (parameterStyleClause != null) {
            return parameterStyleClause;
        }
        SpecificSpecificName specificSpecificName = tryParseSpecificSpecificName();
        if (specificSpecificName != null) {
            return specificSpecificName;
        }
        DeterministicCharacteristic deterministicCharacteristic = tryParseDeterministicCharacteristic();
        if (deterministicCharacteristic != null) {
            return deterministicCharacteristic;
        }
        SqlDataAccessIndication sqlDataAccessIndication = tryParseSqlDataAccessIndication();
        if (sqlDataAccessIndication != null) {
            return sqlDataAccessIndication;
        }
        NullCallClause nullCallClause = tryParseNullCallClause();
        if (nullCallClause != null) {
            return nullCallClause;
        }
        DynamicResultSetsCharacteristic dynamicResultSetsCharacteristic = tryParseDynamicResultSetsCharacteristic();
        if (dynamicResultSetsCharacteristic != null) {
            return dynamicResultSetsCharacteristic;
        }
        SavepointLevelIndication savepointLevelIndication = tryParseSavepointLevelIndication();
        if (savepointLevelIndication != null) {
            return savepointLevelIndication;
        }
        return null;
    }

    // NEW SAVEPOINT LEVEL | OLD SAVEPOINT LEVEL
    private SavepointLevelIndication tryParseSavepointLevelIndication() {
        boolean newSavepointLevel = false;
        boolean oldSavepointLevel = false;
        int markedIndex = mark();
        Token token = scan();
        if (token.isNew()) {
            newSavepointLevel = true;
        } else if (token.isOld()) {
            oldSavepointLevel = true;
        } else {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isSavepoint()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SAVEPOINT.");
        }
        Token token2 = scan();
        if (!token2.isRegularIdentifier() || !"LEVEL".equalsIgnoreCase(token2.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a LEVEL.");
        }
        return new SavepointLevelIndication(token.getBeginIndex(), token2.getEndIndex(), newSavepointLevel, oldSavepointLevel);
    }

    // DYNAMIC RESULT SETS <maximum dynamic result sets>
    private DynamicResultSetsCharacteristic tryParseDynamicResultSetsCharacteristic() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDynamic()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"RESULT".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a RESULT.");
        }
        Token token2 = scan();
        if (!token2.isRegularIdentifier() || !"SETS".equalsIgnoreCase(token2.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a SETS.");
        }
        UnsignedInteger maximumDynamicResultSets = parseUnsignedInteger();
        return new DynamicResultSetsCharacteristic(token.getBeginIndex(), maximumDynamicResultSets);
    }

    //   RETURNS NULL ON NULL INPUT
    // | CALLED ON NULL INPUT
    private NullCallClause tryParseNullCallClause() {
        boolean returnsNullOnNullInput = false;
        boolean calledOnNullInput = false;
        int markedIndex = mark();
        Token token = scan();
        if (token.isReturns()) {
            Token token1 = scan();
            if (!token1.isNull()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a NULL.");
            }
            returnsNullOnNullInput = true;
        } else if (token.isCalled()) {
            calledOnNullInput = true;
        } else {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isOn()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ON.");
        }
        Token token2 = scan();
        if (!token2.isNull()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a NULL.");
        }
        Token token3 = scan();
        if (!token3.isInput()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a INPUT.");
        }
        return new NullCallClause(token.getBeginIndex(), token3.getEndIndex(), returnsNullOnNullInput, calledOnNullInput);
    }

    //   NO SQL
    // | CONTAINS SQL
    // | READS SQL DATA
    // | MODIFIES SQL DATA
    private SqlDataAccessIndication tryParseSqlDataAccessIndication() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isNo()) {
            Token token1 = scan();
            if (!token1.isSql()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SQL.");
            }
            return new SqlDataAccessIndication(
                    token.getBeginIndex(),
                    token1.getEndIndex(),
                    true,
                    false,
                    false,
                    false
            );
        } else if (token.isRegularIdentifier() && "CONTAINS".equalsIgnoreCase(token.getContent())) {
            Token token1 = scan();
            if (!token1.isSql()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SQL.");
            }
            return new SqlDataAccessIndication(
                    token.getBeginIndex(),
                    token1.getEndIndex(),
                    false,
                    true,
                    false,
                    false
            );
        } else if (token.isReads()) {
            Token token1 = scan();
            if (!token1.isSql()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SQL.");
            }
            Token token2 = scan();
            if (!token2.isRegularIdentifier() || !"".equalsIgnoreCase(token2.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a DATA.");
            }
            return new SqlDataAccessIndication(
                    token.getBeginIndex(),
                    token2.getEndIndex(),
                    false,
                    false,
                    true,
                    false
            );
        } else if (token.isModifies()) {
            Token token1 = scan();
            if (!token1.isSql()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SQL.");
            }
            Token token2 = scan();
            if (!token2.isRegularIdentifier() || !"".equalsIgnoreCase(token2.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a DATA.");
            }
            return new SqlDataAccessIndication(
                    token.getBeginIndex(),
                    token2.getEndIndex(),
                    false,
                    false,
                    false,
                    true
            );
        } else {
            reset(markedIndex);
            return null;
        }
    }

    // DETERMINISTIC | NOT DETERMINISTIC
    private DeterministicCharacteristic tryParseDeterministicCharacteristic() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isNot()) {
            Token token1 = scan();
            if (token1.isDeterministic()) {
                return new DeterministicCharacteristic(token.getBeginIndex(), token1.getEndIndex(), true);
            }
        } else if (token.isDeterministic()) {
            return new DeterministicCharacteristic(token.getBeginIndex(), token.getEndIndex(), false);
        }
        reset(markedIndex);
        return null;
    }

    private SpecificSpecificName tryParseSpecificSpecificName() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSpecific()) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName specificName = parseSchemaQualifiedName();
        return new SpecificSpecificName(token.getBeginIndex(), specificName);
    }

    // PARAMETER STYLE { SQL | GENERAL }
    private ParameterStyleClause tryParseParameterStyleClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isParameter()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"STYLE".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a STYLE.");
        }
        Token token2 = scan();
        if (token2.isSql()) {
            return new ParameterStyleClause(token.getBeginIndex(), token2.getEndIndex(), true, false);
        }
        if (token2.isRegularIdentifier() && "GENERAL".equalsIgnoreCase(token2.getContent())) {
            return new ParameterStyleClause(token.getBeginIndex(), token2.getEndIndex(), false, true);
        }
        throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a SQL or GENERAL.");
    }

    private TransliterationDefinition parseTransliterationDefinition() {
        TransliterationDefinition transliterationDefinition = tryParseTranslationDefinition();
        if (transliterationDefinition != null) {
            return transliterationDefinition;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to translation definition.");
    }

    // CREATE TRANSLATION <transliteration name>
    // FOR <source character set specification>
    // TO <target character set specification>
    // FROM <transliteration source>
    private TransliterationDefinition tryParseTranslationDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCreate()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isTranslation()) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName transliterationName = parseSchemaQualifiedName();
        Token token2 = scan();
        if (!token2.isFor()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a FOR.");
        }
        CharacterSetName sourceCharacterSetSpecification = parseCharacterSetName();
        Token token3 = scan();
        if (!token3.isTo()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a TO.");
        }
        CharacterSetName targetCharacterSetSpecification = parseCharacterSetName();
        Token token4 = scan();
        if (!token4.isFrom()) {
            throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a FROM.");
        }
        TransliterationSource transliterationSource = parseTransliterationSource();
        return new TransliterationDefinition(
                token.getBeginIndex(),
                transliterationName,
                sourceCharacterSetSpecification,
                targetCharacterSetSpecification,
                transliterationSource
        );
    }

    // <existing transliteration name> | <transliteration routine>
    private TransliterationSource parseTransliterationSource() {
        SchemaQualifiedName existingTransliterationName = tryParseSchemaQualifiedName();
        if (existingTransliterationName != null) {
            return existingTransliterationName;
        }
        SpecificRoutineDesignator transliterationToutine = tryParseSpecificRoutineDesignator();
        if (transliterationToutine != null) {
            return transliterationToutine;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to transliteration source.");
    }

    // DECLARE <cursor name> [ <cursor sensitivity> ] [ <cursor scrollability> ] CURSOR [ <cursor holdability> ] [ <cursor returnability> ] FOR <cursor specification>
    // DECLARE <cursor name> [ <cursor sensitivity> ] [ <cursor scrollability> ] CURSOR [ <cursor holdability> ] [ <cursor returnability> ] FOR <statement name>
    private DeclareCursorStatement parseDeclareCursorStatement() {
        Token token = scan();
        if (!token.isDeclare()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a DECLARE.");
        }
        LocalQualifiedName cursorName = parseLocalQualifiedName();
        CursorSensitivity cursorSensitivity = tryParseCursorSensitivity();
        CursorScrollability cursorScrollability = tryParseCursorScrollability();
        Token token1 = scan();
        if (!token1.isCursor()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a CURSOR.");
        }
        CursorHoldability cursorHoldability = tryParseCursorHoldability();
        CursorReturnability cursorReturnability = tryParseCursorReturnability();
        Token token2 = scan();
        if (!token2.isFor()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a FOR.");
        }
        CursorSpecification cursorSpecification = tryParseCursorSpecification();
        if (cursorSpecification != null) {
            return new DeclareCursorStatement(
                token.getBeginIndex(),
                cursorSpecification.getEndIndex(),
                cursorName,
                cursorSensitivity,
                cursorScrollability,
                cursorHoldability,
                cursorReturnability,
                cursorSpecification,
                null
            );
        }
        Identifier statementName = tryParseIdentifier();
        if (statementName == null) {
            Token token3 = scan();
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a statement name.");
        }
        return new DeclareCursorStatement(
            token.getBeginIndex(),
            statementName.getEndIndex(),
            cursorName,
            cursorSensitivity,
            cursorScrollability,
            cursorHoldability,
            cursorReturnability,
            null,
            statementName
        );
    }

    // <query expression> [ <order by clause> ] [ <updatability clause> ]
    private CursorSpecification tryParseCursorSpecification() {
        QueryExpression queryExpression = tryParseQueryExpression();
        if (queryExpression == null) {
            return null;
        }
        int endIndex = queryExpression.getEndIndex();
        OrderByClause orderByClause = tryParseOrderByClause();
        if (orderByClause != null) {
            endIndex = orderByClause.getEndIndex();
        }
        UpdatabilityClause updatabilityClause = tryParseUpdatabilityClause();
        if (updatabilityClause != null) {
            endIndex = updatabilityClause.getEndIndex();
        }
        return new CursorSpecification(endIndex, queryExpression, orderByClause, updatabilityClause);
    }

    // ORDER BY <sort specification list>
    private OrderByClause tryParseOrderByClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isOrder()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isBy()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a BY.");
        }
        SortSpecificationList sortSpecificationList = parseSortSpecificationList();
        return new OrderByClause(token.getBeginIndex(), sortSpecificationList);
    }

    // FOR { READ ONLY | UPDATE [ OF <column name list> ] }
    private UpdatabilityClause tryParseUpdatabilityClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isFor()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (token1.isRegularIdentifier() && "READ".equalsIgnoreCase(token1.getContent())) {
            Token token2 = scan();
            if (!token2.isOnly()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ONLY.");
            }
            return new UpdatabilityClause(token.getBeginIndex(), token2.getEndIndex(), true, null);
        }
        if (token1.isUpdate()) {
            IdentifierList columnNameList;
            int markedIndex2 = mark();
            Token token2 = scan();
            if (token2.isOf()) {
                columnNameList = parseIdentifierList();
            } else {
                reset(markedIndex2);
                columnNameList = null;
            }
            return new UpdatabilityClause(token.getBeginIndex(), columnNameList.get(columnNameList.size() - 1).getEndIndex(), false, columnNameList);
        }
        throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a READ or UPDATE.");
    }

    // SENSITIVE | INSENSITIVE | ASENSITIVE
    private CursorSensitivity tryParseCursorSensitivity() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isSensitive()) {
            return new CursorSensitivity(token.getBeginIndex(), token.getEndIndex(), true, false, false);
        }
        if (token.isInsensitive()) {
            return new CursorSensitivity(token.getBeginIndex(), token.getEndIndex(), false, true, false);
        }
        if (token.isAsensitive()) {
            return new CursorSensitivity(token.getBeginIndex(), token.getEndIndex(), false, false, true);
        }
        reset(markedIndex);
        return null;
    }

    // SCROLL | NO SCROLL
    private CursorScrollability tryParseCursorScrollability() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isScroll()) {
            return new CursorScrollability(token.getBeginIndex(), token.getEndIndex(), true, false);
        }
        if (token.isNo()) {
            Token token1 = scan();
            if (!token1.isScroll()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SCROLL.");
            }
            return new CursorScrollability(token.getBeginIndex(), token.getEndIndex(), false, true);
        }
        reset(markedIndex);
        return null;
    }

    // WITH HOLD | WITHOUT HOLD
    private CursorHoldability tryParseCursorHoldability() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isWith()) {
            Token token1 = scan();
            if (!token1.isHold()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a HOLD.");
            }
            return new CursorHoldability(token.getBeginIndex(), token.getEndIndex(), true, false);
        }
        if (token.isWithout()) {
            Token token1 = scan();
            if (!token1.isHold()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a HOLD.");
            }
            return new CursorHoldability(token.getBeginIndex(), token.getEndIndex(), false, true);
        }
        reset(markedIndex);
        return null;
    }

    // WITH RETURN | WITHOUT RETURN
    private CursorReturnability tryParseCursorReturnability() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isWith()) {
            Token token1 = scan();
            if (!token1.isReturn()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a RETURN.");
            }
            return new CursorReturnability(token.getBeginIndex(), token.getEndIndex(), true, false);
        }
        if (token.isWithout()) {
            Token token1 = scan();
            if (!token1.isReturn()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a RETURN.");
            }
            return new CursorReturnability(token.getBeginIndex(), token.getEndIndex(), false, true);
        }
        reset(markedIndex);
        return null;
    }

    private TableDefinition parseTableDefinition() {
        TableDefinition tableDefinition = tryParseTableDefinition();
        if (tableDefinition != null) {
            return tableDefinition;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to table definition.");
    }

    private TriggerDefinition parseTriggerDefinition() {
        TriggerDefinition triggerDefinition = tryParseTriggerDefinition();
        if (triggerDefinition != null) {
            return triggerDefinition;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to trigger definition.");
    }

    // CREATE TRIGGER
    // <trigger name>
    // <trigger action time>
    // <trigger event>
    // ON
    // <table name>
    // [ REFERENCING <old or new values alias list> ]
    // <triggered action>
    private TriggerDefinition tryParseTriggerDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCreate()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isTrigger()) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName triggerName = parseSchemaQualifiedName();
        TriggerActionTime triggerActionTime = parseTriggerActionTime();
        TriggerEvent triggerEvent = parseTriggerEvent();
        Token token2 = scan();
        if (!token2.isOn()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ON.");
        }
        LocalOrSchemaQualifiedName tableName = parseLocalOrSchemaQualifiedName();
        List<OldOrNewValuesAlias> oldOrNewValuesAliasList = null;
        int markedIndex3 = mark();
        Token token3 = scan();
        if (token3.isReferencing()) {
            OldOrNewValuesAlias oldOrNewValuesAlias = parseOldOrNewValuesAlias();
            oldOrNewValuesAliasList = new ArrayList<>();
            oldOrNewValuesAliasList.add(oldOrNewValuesAlias);
            while (true) {
                OldOrNewValuesAlias oldOrNewValuesAlias1 = tryParseOldOrNewValuesAlias();
                if (oldOrNewValuesAlias1 == null) {
                    break;
                }
                oldOrNewValuesAliasList.add(oldOrNewValuesAlias1);
            }
        } else {
            reset(markedIndex3);
        }
        TriggeredAction triggeredAction = parseTriggeredAction();
        return new TriggerDefinition(
                token.getBeginIndex(),
                triggerName,
                triggerActionTime,
                triggerEvent,
                tableName,
                oldOrNewValuesAliasList == null ? null : new OldOrNewValuesAliasList(oldOrNewValuesAliasList),
                triggeredAction
        );
    }

    private OldOrNewValuesAlias parseOldOrNewValuesAlias() {
        int markedIndex = mark();
        Token token = scan();
        reset(markedIndex);
        OldOrNewValuesAlias oldOrNewValuesAlias = tryParseOldOrNewValuesAlias();
        if (oldOrNewValuesAlias != null) {
            return oldOrNewValuesAlias;
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to old or new values alias.");
    }

    //   OLD [ ROW ] [ AS ] <old values correlation name>
    // | NEW [ ROW ] [ AS ] <new values correlation name>
    // | OLD TABLE [ AS ] <old values table alias>
    // | NEW TABLE [ AS ] <new values table alias>
    private OldOrNewValuesAlias tryParseOldOrNewValuesAlias() {
        boolean old;
        boolean new_;
        boolean row;
        boolean table;
        int markedIndex = mark();
        Identifier correlationNameOrTableAlias;
        Token token = scan();
        if (token.isOld()) {
            old = true;
            new_ = false;
        } else if (token.isNew()) {
            old = false;
            new_ = true;
        } else {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (token1.isRow()) {
            row = true;
            table = false;
            int markedIndex2 = mark();
            Token token2 = scan();
            if (!token2.isAs()) {
                reset(markedIndex2);
            }
        } else if (token1.isAs()) {
            row = true;
            table = false;
        } else if (token1.isTable()) {
            row = false;
            table = true;
            int markedIndex2 = mark();
            Token token2 = scan();
            if (!token2.isAs()) {
                reset(markedIndex2);
            }
        } else {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ROW, AS or TABLE.");
        }
        correlationNameOrTableAlias = parseIdentifier();
        return new OldOrNewValuesAlias(token.getBeginIndex(), old, new_, row, table, correlationNameOrTableAlias);
    }

    // [ FOR EACH { ROW | STATEMENT } ] [ WHEN <left paren> <search condition> <right paren> ] <triggered SQL statement>
    private TriggeredAction parseTriggeredAction() {
        boolean forEachRow = false;
        boolean forEachStatement = false;
        int markedIndex = mark();
        Token token = scan();
        if (token.isFor()) {
            Token token1 = scan();
            if (!token.isEach()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a EACH.");
            }
            Token token2 = scan();
            if (token2.isRow()) {
                forEachRow = true;
            } else if (token2.isRegularIdentifier() && "".equalsIgnoreCase(token2.getContent())) {
                forEachStatement = true;
            } else {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ROW or STATEMENT.");
            }
            markedIndex = mark();
            token = scan();
        }
        BooleanValueExpression searchCondition = null;
        if (token.isWhen()) {
            Token token1 = scan();
            if (!token1.isLeftParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
            }
            searchCondition = parseBooleanValueExpression();
            Token token2 = scan();
            if (!token2.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
            }
            markedIndex = mark();
            token = scan();
        }
        reset(markedIndex);
        TriggeredSqlStatement triggeredSqlStatement = parseTriggeredSqlStatement();
        return new TriggeredAction(
            token.getBeginIndex(),
            forEachRow,
            forEachStatement,
            searchCondition,
            triggeredSqlStatement
        );
    }

    //  <SQL procedure statement> | BEGIN ATOMIC { <SQL procedure statement> <semicolon> }... END
    private TriggeredSqlStatement parseTriggeredSqlStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isBegin()) {
            Token token1 = scan();
            if (!token1.isAtomic()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ");
            }
            SqlExecutableStatement sqlProcedureStatement = parseSqlExecutableStatement();
            List<SqlExecutableStatement> sqlProcedureStatementList = new ArrayList<>();
            sqlProcedureStatementList.add(sqlProcedureStatement);
            Token token2 = scan();
            if (!token2.isSemicolon()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ;.");
            }
            while (true) {
                SqlExecutableStatement sqlProcedureStatement1 = tryParseSqlExecutableStatement();
                if (sqlProcedureStatement1 == null) {
                    break;
                }
                sqlProcedureStatementList.add(sqlProcedureStatement1);
                Token token3 = scan();
                if (!token3.isSemicolon()) {
                    throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ;.");
                }
            }
            Token token3 = scan();
            if (!token3.isEnd()) {
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a END.");
            }
            return new TriggeredSqlStatement(
                    token.getBeginIndex(),
                    token3.getEndIndex(),
                    new SqlExecutableStatementList(sqlProcedureStatementList)
            );
        }
        reset(markedIndex);
        SqlExecutableStatement sqlProcedureStatement = tryParseSqlExecutableStatement();
        if (sqlProcedureStatement == null) {
            Token token1 = scan();
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Cannot resolve to triggered SQL statement.");
        }
        return new TriggeredSqlStatement(sqlProcedureStatement);
    }

    private SqlExecutableStatement parseSqlExecutableStatement() {
        SqlExecutableStatement sqlExecutableStatement = tryParseSqlExecutableStatement();
        if (sqlExecutableStatement != null) {
            return sqlExecutableStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to SQL executable statement.");
    }

    //   <SQL schema statement>
    // | <SQL data statement>
    // | <SQL control statement>
    // | <SQL transaction statement>
    // | <SQL connection statement>
    // | <SQL session statement>
    // | <SQL diagnostics statement>
    // | <SQL dynamic statement>
    private SqlExecutableStatement tryParseSqlExecutableStatement() {
        SqlSchemaStatement sqlSchemaStatement = tryParseSqlSchemaStatement();
        if (sqlSchemaStatement != null) {
            return sqlSchemaStatement;
        }
        SqlDataStatement sqlDataStatement = tryParseSqlDataStatement();
        if (sqlDataStatement != null) {
            return sqlDataStatement;
        }
        SqlControlStatement sqlControlStatement = tryParseSqlControlStatement();
        if (sqlControlStatement != null) {
            return sqlControlStatement;
        }
        SqlTransactionStatement sqlTransactionStatement = tryParseSqlTransactionStatement();
        if (sqlTransactionStatement != null) {
            return sqlTransactionStatement;
        }
        SqlConnectionStatement sqlConnectionStatement = tryParseSqlConnectionStatement();
        if (sqlConnectionStatement != null) {
            return sqlConnectionStatement;
        }
        SqlSessionStatement sqlSessionStatement = tryParseSqlSessionStatement();
        if (sqlSessionStatement != null) {
            return sqlSessionStatement;
        }
        GetDiagnosticsStatement getDiagnosticsStatement = tryParseGetDiagnosticsStatement();
        if (getDiagnosticsStatement != null) {
            return getDiagnosticsStatement;
        }
        SqlDynamicStatement sqlDynamicStatement = tryParseSqlDynamicStatement();
        if (sqlDynamicStatement != null) {
            return sqlDynamicStatement;
        }
        return null;
    }

    //   <SQL schema definition statement>
    // | <SQL schema manipulation statement>
    private SqlSchemaStatement tryParseSqlSchemaStatement() {
        SqlSchemaDefinitionStatement sqlSchemaDefinitionStatement
                = tryParseSqlSchemaDefinitionStatement();
        if (sqlSchemaDefinitionStatement != null) {
            return sqlSchemaDefinitionStatement;
        }
        SqlSchemaManipulationStatement sqlSchemaManipulationStatement
                = tryParseSqlSchemaManipulationStatement();
        if (sqlSchemaManipulationStatement != null) {
            return sqlSchemaManipulationStatement;
        }
        return null;
    }

    //   <schema definition>
    // | <table definition>
    // | <view definition>
    // | <SQL-invoked routine>
    // | <grant statement>
    // | <role definition>
    // | <domain definition>
    // | <character set definition>
    // | <collation definition>
    // | <transliteration definition>
    // | <assertion definition>
    // | <trigger definition>
    // | <user-defined type definition>
    // | <user-defined cast definition>
    // | <user-defined ordering definition>
    // | <transform definition>
    // | <sequence generator definition>
    private SqlSchemaDefinitionStatement tryParseSqlSchemaDefinitionStatement() {
        SchemaDefinition schemaDefinition = tryParseSchemaDefinition();
        if (schemaDefinition != null) {
            return schemaDefinition;
        }
        TableDefinition tableDefinition = tryParseTableDefinition();
        if (tableDefinition != null) {
            return tableDefinition;
        }
        ViewDefinition viewDefinition = tryParseViewDefinition();
        if (viewDefinition != null) {
            return viewDefinition;
        }
        SchemaRoutine sqlInvokedRoutine = tryParseSchemaRoutine();
        if (sqlInvokedRoutine != null) {
            return sqlInvokedRoutine;
        }
        GrantStatement grantStatement = tryParseGrantStatement();
        if (grantStatement != null) {
            return grantStatement;
        }
        RoleDefinition roleDefinition = tryParseRoleDefinition();
        if (roleDefinition != null) {
            return roleDefinition;
        }
        DomainDefinition domainDefinition = tryParseDomainDefinition();
        if (domainDefinition != null) {
            return domainDefinition;
        }
        CharacterSetDefinition characterSetDefinition = tryParseCharacterSetDefinition();
        if (characterSetDefinition != null) {
            return characterSetDefinition;
        }
        CollationDefinition collationDefinition = tryParseCollationDefinition();
        if (collationDefinition != null) {
            return collationDefinition;
        }
        TransliterationDefinition transliterationDefinition = tryParseTranslationDefinition();
        if (transliterationDefinition != null) {
            return transliterationDefinition;
        }
        AssertionDefinition assertionDefinition = tryParseAssertionDefinition();
        if (assertionDefinition != null) {
            return assertionDefinition;
        }
        TriggerDefinition triggerDefinition = tryParseTriggerDefinition();
        if (triggerDefinition != null) {
            return triggerDefinition;
        }
        UserDefinedTypeDefinition userDefinedTypeDefinition
                = tryParseUserDefinedTypeDefinition();
        if (userDefinedTypeDefinition != null) {
            return userDefinedTypeDefinition;
        }
        UserDefinedCastDefinition userDefinedCastDefinition
                = tryParseUserDefinedCastDefinition();
        if (userDefinedCastDefinition != null) {
            return userDefinedCastDefinition;
        }
        UserDefinedOrderingDefinition userDefinedOrderingDefinition
                = tryParseUserDefinedOrderingDefinition();
        if (userDefinedOrderingDefinition != null) {
            return userDefinedOrderingDefinition;
        }
        TransformDefinition transformDefinition = tryParseTransformDefinition();
        if (transformDefinition != null) {
            return transformDefinition;
        }
        SequenceGeneratorDefinition sequenceGeneratorDefinition
                = tryParseSequenceGeneratorDefinition();
        if (sequenceGeneratorDefinition != null) {
            return sequenceGeneratorDefinition;
        }
        return null;
    }

    //   <drop schema statement>
    // | <alter table statement>
    // | <drop table statement>
    // | <drop view statement>
    // | <alter routine statement>
    // | <drop routine statement>
    // | <drop user-defined cast statement>
    // | <revoke statement>
    // | <drop role statement>
    // | <alter domain statement>
    // | <drop domain statement>
    // | <drop character set statement>
    // | <drop collation statement>
    // | <drop transliteration statement>
    // | <drop assertion statement>
    // | <drop trigger statement>
    // | <alter type statement>
    // | <drop data type statement>
    // | <drop user-defined ordering statement>
    // | <alter transform statement>
    // | <drop transform statement>
    // | <alter sequence generator statement>
    // | <drop sequence generator statement>
    private SqlSchemaManipulationStatement tryParseSqlSchemaManipulationStatement() {
        DropSchemaStatement dropSchemaStatement = tryParseDropSchemaStatement();
        if (dropSchemaStatement != null) {
            return dropSchemaStatement;
        }
        AlterTableStatement alterTableStatement = tryParseAlterTableStatement();
        if (alterTableStatement != null) {
            return alterTableStatement;
        }
        DropTableStatement dropTableStatement = tryParseDropTableStatement();
        if (dropTableStatement != null) {
            return dropTableStatement;
        }
        DropViewStatement dropViewStatement = tryParseDropViewStatement();
        if (dropViewStatement != null) {
            return dropViewStatement;
        }
        AlterRoutineStatement alterRoutineStatement = tryParseAlterRoutineStatement();
        if (alterRoutineStatement != null) {
            return alterRoutineStatement;
        }
        DropRoutineStatement dropRoutineStatement = tryParseDropRoutineStatement();
        if (dropRoutineStatement != null) {
            return dropRoutineStatement;
        }
        DropUserDefinedCastStatement dropUserDefinedCastStatement
                = tryParseDropUserDefinedCastStatement();
        if (dropUserDefinedCastStatement != null) {
            return dropUserDefinedCastStatement;
        }
        RevokeStatement revokeStatement = tryParseRevokeStatement();
        if (revokeStatement != null) {
            return revokeStatement;
        }
        DropRoleStatement dropRoleStatement = tryParseDropRoleStatement();
        if (dropRoleStatement != null) {
            return dropRoleStatement;
        }
        AlterDomainStatement alterDomainStatement = tryParseAlterDomainStatement();
        if (alterDomainStatement != null) {
            return alterDomainStatement;
        }
        DropDomainStatement dropDomainStatement = tryParseDropDomainStatement();
        if (dropDomainStatement != null) {
            return dropDomainStatement;
        }
        DropCharacterSetStatement dropCharacterSetStatement = tryParseDropCharacterSetStatement();
        if (dropCharacterSetStatement != null) {
            return dropCharacterSetStatement;
        }
        DropCollationStatement dropCollationStatement = tryParseDropCollationStatement();
        if (dropCollationStatement != null) {
            return dropCollationStatement;
        }
        DropTransliterationStatement dropTransliterationStatement = tryParseDropTranslationStatement();
        if (dropTransliterationStatement != null) {
            return dropTransliterationStatement;
        }
        DropAssertionStatement dropAssertionStatement = tryParseDropAssertionStatement();
        if (dropAssertionStatement != null) {
            return dropAssertionStatement;
        }
        DropTriggerStatement dropTriggerStatement = tryParseDropTriggerStatement();
        if (dropTriggerStatement != null) {
            return dropTriggerStatement;
        }
        AlterTypeStatement alterTypeStatement = tryParseAlterTypeStatement();
        if (alterTypeStatement != null) {
            return alterTypeStatement;
        }
        DropDataTypeStatement dropDataTypeStatement = tryParseDropDataTypeStatement();
        if (dropDataTypeStatement != null) {
            return dropDataTypeStatement;
        }
        DropUserDefinedOrderingStatement dropUserDefinedOrderingStatement
                = tryParseDropUserDefinedOrderingStatement();
        if (dropUserDefinedOrderingStatement != null) {
            return dropUserDefinedOrderingStatement;
        }
        AlterTransformStatement alterTransformStatement = tryParseAlterTransformStatement();
        if (alterTransformStatement != null) {
            return alterTransformStatement;
        }
        DropTransformStatement dropTransformStatement = tryParseDropTransformStatement();
        if (dropTransformStatement != null) {
            return dropTransformStatement;
        }
        AlterSequenceGeneratorStatement alterSequenceGeneratorStatement
                = tryParseAlterSequenceGeneratorStatement();
        if (alterSequenceGeneratorStatement != null) {
            return alterSequenceGeneratorStatement;
        }
        DropSequenceGeneratorStatement dropSequenceGeneratorStatement
                = tryParseDropSequenceGeneratorStatement();
        if (dropSequenceGeneratorStatement != null) {
            return dropSequenceGeneratorStatement;
        }
        return null;
    }

    // ALTER TYPE <schema-resolved user-defined type name> <alter type action>
    private AlterTypeStatement tryParseAlterTypeStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAlter()) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName schemaResolvedUserDefinedTypeName
                = parseSchemaQualifiedName();
        AlterTypeAction alterTypeAction = parseAlterTypeAction();
        return new AlterTypeStatement(
                token.getBeginIndex(),
                schemaResolvedUserDefinedTypeName,
                alterTypeAction
        );
    }

    // <alter type action>
    // ::= <add attribute definition>
    //   | <drop attribute definition>
    //   | <add original method specification>
    //   | <add overriding method specification>
    //   | <drop method specification>
    private AlterTypeAction parseAlterTypeAction() {
        AddAttributeDefinition addAttributeDefinition = tryParseAddAttributeDefinition();
        if (addAttributeDefinition != null) {
            return addAttributeDefinition;
        }
        DropAttributeDefinition dropAttributeDefinition = tryParseDropAttributeDefinition();
        if (dropAttributeDefinition != null) {
            return dropAttributeDefinition;
        }
        AddOriginalMethodSpecification addOriginalMethodSpecification
                = tryParseAddOriginalMethodSpecification();
        if (addOriginalMethodSpecification != null) {
            return addOriginalMethodSpecification;
        }
        AddOverridingMethodSpecification addOverridingMethodSpecification
                = tryParseAddOverridingMethodSpecification();
        if (addOverridingMethodSpecification != null) {
            return addOverridingMethodSpecification;
        }
        DropMethodSpecification dropMethodSpecification = tryParseDropMethodSpecification();
        if (dropMethodSpecification != null) {
            return dropMethodSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to alter type action.");
    }

    // <drop method specification> ::= DROP <specific method specification designator> RESTRICT
    private DropMethodSpecification tryParseDropMethodSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        SpecificMethodSpecificationDesignator specificMethodSpecificationDesignator
                = parseSpecificMethodSpecificationDesignator();
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"RESTRICT".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a RESTRICT.");
        }
        return new DropMethodSpecification(
                token.getBeginIndex(),
                token1.getEndIndex(),
                specificMethodSpecificationDesignator
        );
    }

    // <specific method specification designator>
    // ::= [ INSTANCE | STATIC | CONSTRUCTOR ]
    //     METHOD
    //     <method name>
    //     <data type list>
    private SpecificMethodSpecificationDesignator parseSpecificMethodSpecificationDesignator() {
        boolean instance = false;
        boolean static_ = false;
        boolean constructor = false;
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "INSTANCE".equalsIgnoreCase(token.getContent())) {
            instance = true;
        } else if (token.isStatic()) {
            static_ = true;
        } else if (token.isRegularIdentifier() && "CONSTRUCTOR".equalsIgnoreCase(token.getContent())) {
            constructor = true;
        } else {
            reset(markedIndex);
        }
        int beginIndex = token.getBeginIndex();
        Token token1 = scan();
        if (!token1.isMethod()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a METHOD.");
        }
        Identifier methodName = parseIdentifier();
        DataTypeList dataTypeList = parseDataTypeList();
        return new SpecificMethodSpecificationDesignator(
                beginIndex,
                instance,
                static_,
                constructor,
                methodName,
                dataTypeList
        );
    }

    private DataTypeList parseDataTypeList() {
        DataTypeList dataTypeList = tryParseDataTypeList();
        if (dataTypeList != null) {
            return dataTypeList;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to data type list.");
    }

    // <left paren> [ <data type> [ { <comma> <data type> }... ] ] <right paren>
    private DataTypeList tryParseDataTypeList() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        DataType dataType = parseDataType();
        List<DataType> dataTypeList = new ArrayList<>();
        dataTypeList.add(dataType);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            DataType dataType1 = parseDataType();
            dataTypeList.add(dataType1);
        }
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new DataTypeList(token.getBeginIndex(), token1.getEndIndex(), dataTypeList);
    }

    // <add overriding method specification> ::= ADD <overriding method specification>
    private AddOverridingMethodSpecification tryParseAddOverridingMethodSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAdd()) {
            reset(markedIndex);
            return null;
        }
        OverridingMethodSpecification overridingMethodSpecification
                = parseOverridingMethodSpecification();
        return new AddOverridingMethodSpecification(
                token.getBeginIndex(),
                overridingMethodSpecification
        );
    }

    // <add original method specification> ::= ADD <original method specification>
    private AddOriginalMethodSpecification tryParseAddOriginalMethodSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAdd()) {
            reset(markedIndex);
            return null;
        }
        OriginalMethodSpecification originalMethodSpecification
                = parseOriginalMethodSpecification();
        return new AddOriginalMethodSpecification(
                token.getBeginIndex(),
                originalMethodSpecification
        );
    }

    // <drop attribute definition> ::= DROP ATTRIBUTE <attribute name> RESTRICT
    private DropAttributeDefinition tryParseDropAttributeDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"ATTRIBUTE".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        Identifier attributeName = parseIdentifier();
        Token token2 = scan();
        if (!token2.isRegularIdentifier() || !"RESTRICT".equalsIgnoreCase(token2.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a RESTRICT.");
        }
        return new DropAttributeDefinition(
                token.getBeginIndex(),
                token2.getEndIndex(),
                attributeName
        );
    }

    // <add attribute definition> ::= ADD ATTRIBUTE <attribute definition>
    private AddAttributeDefinition tryParseAddAttributeDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAdd()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"ATTRIBUTE".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        AttributeDefinition attributeDefinition = parseAttributeDefinition();
        return new AddAttributeDefinition(token.getBeginIndex(), attributeDefinition);
    }

    // ALTER {TRANSFORM | TRANSFORMS} FOR <schema-resolved user-defined type name> <alter group> ...
    private AlterTransformStatement tryParseAlterTransformStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAlter()) {
            reset(markedIndex);
            return null;
        }
        boolean transform = false;
        boolean transforms = false;
        Token token1 = scan();
        if (token1.isRegularIdentifier()) {
            if ("TRANSFORM".equalsIgnoreCase(token1.getContent())) {
                transform = true;
            } else if ("TRANSFORMS".equalsIgnoreCase(token1.getContent())) {
                transforms = true;
            } else {
                reset(markedIndex);
                return null;
            }
        } else {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isFor()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a FOR.");
        }
        SchemaQualifiedName schemaResolvedUserDefinedTypeName = parseSchemaQualifiedName();
        AlterGroup alterGroup = parseAlterGroup();
        List<AlterGroup> alterGroupList = new ArrayList<>();
        alterGroupList.add(alterGroup);
        while (true) {
            AlterGroup alterGroup1 = tryParseAlterGroup();
            if (alterGroup1 == null) {
                break;
            }
            alterGroupList.add(alterGroup1);
        }
        return new AlterTransformStatement(
                token.getBeginIndex(),
                transform,
                transforms,
                schemaResolvedUserDefinedTypeName,
                new AlterGroupList(alterGroupList)
        );
    }

    private AlterGroup parseAlterGroup() {
        AlterGroup alterGroup = tryParseAlterGroup();
        if (alterGroup != null) {
            return alterGroup;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to alter group.");
    }

    // <alter group> ::= <group name> <left paren> <alter transform action list> <right paren>
    private AlterGroup tryParseAlterGroup() {
        int markedIndex = mark();
        Identifier groupName = tryParseIdentifier();
        if (groupName == null) {
            return null;
        }
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        AlterTransformAction alterTransformAction = tryParseAlterTransformAction();
        if (alterTransformAction == null) {
            reset(markedIndex);
            return null;
        }
        List<AlterTransformAction> alterTransformActionList = new ArrayList<>();
        alterTransformActionList.add(alterTransformAction);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            AlterTransformAction alterTransformAction1 = parseAlterTransformAction();
            alterTransformActionList.add(alterTransformAction1);
        }
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new AlterGroup(
                token1.getEndIndex(),
                groupName,
                new AlterTransformActionList(alterTransformActionList)
        );
    }

    private AlterTransformAction parseAlterTransformAction() {
        AlterTransformAction alterTransformAction = tryParseAlterTransformAction();
        if (alterTransformAction != null) {
            return alterTransformAction;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to alter transform action.");
    }

    // <alter transform action>
    // ::= <add transform element list>
    //   | <drop transform element list>
    private AlterTransformAction tryParseAlterTransformAction() {
        AddTransformElementList addTransformElementList = tryParseAddTransformElementList();
        if (addTransformElementList != null) {
            return addTransformElementList;
        }
        DropTransformElementList dropTransformElementList = tryParseDropTransformElementList();
        if (dropTransformElementList != null) {
            return dropTransformElementList;
        }
        return null;
    }

    // ADD <left paren> <transform element list> <right paren>
    private AddTransformElementList tryParseAddTransformElementList() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAdd()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        TransformElementList transformElementList = parseTransformElementList();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new AddTransformElementList(
                token.getBeginIndex(),
                token2.getEndIndex(),
                transformElementList
        );
    }

    // DROP <left paren> <transform kind> [ <comma> <transform kind> ] <drop behavior> <right paren>
    private DropTransformElementList tryParseDropTransformElementList() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        TransformKind transformKind1 = parseTransformKind();
        TransformKind transformKind2;
        int markedIndex2 = mark();
        Token token2 = scan();
        if (token2.isComma()) {
            transformKind2 = parseTransformKind();
        } else {
            transformKind2 = null;
            reset(markedIndex2);
        }
        DropBehavior dropBehavior = parseDropBehavior();
        Token token3 = scan();
        if (!token3.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
        }
        return new DropTransformElementList(
                token.getBeginIndex(),
                token3.getEndIndex(),
                transformKind1,
                transformKind2,
                dropBehavior
        );
    }

    // TO SQL | FROM SQL
    private TransformKind parseTransformKind() {
        Token token = scan();
        if (!token.isTo() && !token.isFrom()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to transform kind.");
        }
        Token token1 = scan();
        if (!token1.isSql()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SQL.");
        }
        boolean toSql;
        boolean fromSql;
        if (token.isTo()) {
            toSql = true;
            fromSql = false;
        } else {
            toSql = false;
            fromSql = true;
        }
        return new TransformKind(token.getBeginIndex(), token1.getEndIndex(), toSql, fromSql);
    }

    // <alter sequence generator statement>
    // ::= ALTER SEQUENCE <sequence generator name> <alter sequence generator options>
    private AlterSequenceGeneratorStatement tryParseAlterSequenceGeneratorStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAlter()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"SEQUENCE".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName sequenceGeneratorName = parseSchemaQualifiedName();
        AlterSequenceGeneratorOptions alterSequenceGeneratorOptions
                = parseAlterSequenceGeneratorOptions();
        return new AlterSequenceGeneratorStatement(
                token.getBeginIndex(),
                sequenceGeneratorName,
                alterSequenceGeneratorOptions
        );
    }

    // <alter sequence generator options> ::= <alter sequence generator option> ...
    private AlterSequenceGeneratorOptions parseAlterSequenceGeneratorOptions() {
        AlterSequenceGeneratorOption alterSequenceGeneratorOption
                = parseAlterSequenceGeneratorOption();
        List<AlterSequenceGeneratorOption> alterSequenceGeneratorOptionList
                = new ArrayList<>();
        alterSequenceGeneratorOptionList.add(alterSequenceGeneratorOption);
        while (true) {
            AlterSequenceGeneratorOption alterSequenceGeneratorOption1
                    = tryParseAlterSequenceGeneratorOption();
            if (alterSequenceGeneratorOption1 == null) {
                break;
            }
            alterSequenceGeneratorOptionList.add(alterSequenceGeneratorOption1);
        }
        return new AlterSequenceGeneratorOptions(alterSequenceGeneratorOptionList);
    }

    private AlterSequenceGeneratorOption parseAlterSequenceGeneratorOption() {
        AlterSequenceGeneratorOption alterSequenceGeneratorOption
                = tryParseAlterSequenceGeneratorOption();
        if (alterSequenceGeneratorOption != null) {
            return alterSequenceGeneratorOption;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to alter sequence generator option.");
    }

    // <alter sequence generator option>
    // ::= <alter sequence generator restart option>
    //   | <basic sequence generator option>
    private AlterSequenceGeneratorOption tryParseAlterSequenceGeneratorOption() {
        AlterSequenceGeneratorRestartOption alterSequenceGeneratorRestartOption
                = tryParseAlterSequenceGeneratorRestartOption();
        if (alterSequenceGeneratorRestartOption != null) {
            return alterSequenceGeneratorRestartOption;
        }
        BasicSequenceGeneratorOption basicSequenceGeneratorOption
                = tryParseBasicSequenceGeneratorOption();
        if (basicSequenceGeneratorOption != null) {
            return basicSequenceGeneratorOption;
        }
        return null;
    }

    // ALTER <specific routine designator> <alter routine characteristics> <alter routine behavior>
    private AlterRoutineStatement tryParseAlterRoutineStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAlter()) {
            reset(markedIndex);
            return null;
        }
        SpecificRoutineDesignator specificRoutineDesignator
                = tryParseSpecificRoutineDesignator();
        if (specificRoutineDesignator == null) {
            reset(markedIndex);
            return null;
        }
        AlterRoutineCharacteristics alterRoutineCharacteristics
                = parseAlterRoutineCharacteristics();
        AlterRoutineBehavior alterRoutineBehavior
                = parseAlterRoutineBehavior();
        return new AlterRoutineStatement(
                token.getBeginIndex(),
                specificRoutineDesignator,
                alterRoutineCharacteristics,
                alterRoutineBehavior
        );
    }

    // <alter routine characteristics> ::= <alter routine characteristic> ...
    private AlterRoutineCharacteristics parseAlterRoutineCharacteristics() {
        AlterRoutineCharacteristic alterRoutineCharacteristic
                = parseAlterRoutineCharacteristic();
        List<AlterRoutineCharacteristic> alterRoutineCharacteristicList
                = new ArrayList<>();
        alterRoutineCharacteristicList.add(alterRoutineCharacteristic);
        while (true) {
            AlterRoutineCharacteristic alterRoutineCharacteristic1
                    = tryParseAlterRoutineCharacteristic();
            if (alterRoutineCharacteristic1 == null) {
                break;
            }
            alterRoutineCharacteristicList.add(alterRoutineCharacteristic1);
        }
        return new AlterRoutineCharacteristics(alterRoutineCharacteristicList);
    }

    private AlterRoutineCharacteristic parseAlterRoutineCharacteristic() {
        AlterRoutineCharacteristic alterRoutineCharacteristic
                = tryParseAlterRoutineCharacteristic();
        if (alterRoutineCharacteristic != null) {
            return alterRoutineCharacteristic;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to alter routine characteristic.");
    }

    //   <language clause>
    // | <parameter style clause>
    // | <SQL-data access indication>
    // | <null-call clause>
    // | <dynamic result sets characteristic>
    // | NAME <external routine name>
    private AlterRoutineCharacteristic tryParseAlterRoutineCharacteristic() {
        LanguageClause languageClause = tryParseLanguageClause();
        if (languageClause != null) {
            return languageClause;
        }
        ParameterStyleClause parameterStyleClause = tryParseParameterStyleClause();
        if (parameterStyleClause != null) {
            return parameterStyleClause;
        }
        SqlDataAccessIndication sqlDataAccessIndication = tryParseSqlDataAccessIndication();
        if (sqlDataAccessIndication != null) {
            return sqlDataAccessIndication;
        }
        NullCallClause nullCallClause = tryParseNullCallClause();
        if (nullCallClause != null) {
            return nullCallClause;
        }
        DynamicResultSetsCharacteristic dynamicResultSetsCharacteristic
                = tryParseDynamicResultSetsCharacteristic();
        if (dynamicResultSetsCharacteristic != null) {
            return dynamicResultSetsCharacteristic;
        }
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "NAME".equalsIgnoreCase(token.getContent())) {
            ExternalRoutineName externalRoutineName = tryParseExternalRoutineName();
            if (externalRoutineName == null) {
                reset(markedIndex);
                return null;
            }
            return new NameExternalRoutineName(token.getBeginIndex(), externalRoutineName);
        }
        reset(markedIndex);
        return null;
    }

    private ExternalRoutineName parseExternalRoutineName() {
        ExternalRoutineName externalRoutineName = tryParseExternalRoutineName();
        if (externalRoutineName != null) {
            return externalRoutineName;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to external routine name.");
    }

    // <identifier> | <character string literal>
    private ExternalRoutineName tryParseExternalRoutineName() {
        Identifier identifier = tryParseIdentifier();
        if (identifier != null) {
            return identifier;
        }
        CharacterStringLiteral characterStringLiteral = tryParseCharacterStringLiteral();
        if (characterStringLiteral != null) {
            return characterStringLiteral;
        }
        return null;
    }

    // <alter routine behavior> ::= RESTRICT
    private AlterRoutineBehavior parseAlterRoutineBehavior() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"RESTRICT".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        return new AlterRoutineBehavior(token.getBeginIndex(), token.getEndIndex());
    }

    private AlterTableStatement parseAlterTableStatement() {
        AlterTableStatement alterTableStatement = tryParseAlterTableStatement();
        if (alterTableStatement != null) {
            return alterTableStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to alter table statement.");
    }

    // ALTER TABLE <table name> <alter table action>
    private AlterTableStatement tryParseAlterTableStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAlter()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isTable()) {
            reset(markedIndex);
            return null;
        }
        LocalOrSchemaQualifiedName tableName = parseLocalOrSchemaQualifiedName();
        AlterTableAction alterTableAction = parseAlterTableAction();
        return new AlterTableStatement(token.getBeginIndex(), tableName, alterTableAction);
    }

    private AlterDomainStatement parseAlterDomainStatement() {
        AlterDomainStatement alterDomainStatement = tryParseAlterDomainStatement();
        if (alterDomainStatement != null) {
            return alterDomainStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to alter domain statement.");
    }

    // <alter domain statement>::= ALTER DOMAIN <domain name> <alter domain action>
    private AlterDomainStatement tryParseAlterDomainStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAlter()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"DOMAIN".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName domainName = parseSchemaQualifiedName();
        AlterDomainAction alterDomainAction = parseAlterDomainAction();
        return new AlterDomainStatement(token.getBeginIndex(), domainName, alterDomainAction);
    }

    // <drop sequence generator statement> ::= DROP SEQUENCE <sequence generator name> <drop behavior>
    private DropSequenceGeneratorStatement tryParseDropSequenceGeneratorStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"SEQUENCE".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName sequenceGeneratorName = parseSchemaQualifiedName();
        DropBehavior dropBehavior = parseDropBehavior();
        return new DropSequenceGeneratorStatement(
                token.getBeginIndex(),
                sequenceGeneratorName,
                dropBehavior
        );
    }

    // <drop transform statement> ::=
    // DROP { TRANSFORM | TRANSFORMS } <transforms to be dropped> FOR <schema-resolved user-defined type name> <drop behavior>
    private DropTransformStatement tryParseDropTransformStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        boolean transform = false;
        boolean transforms = false;
        Token token1 = scan();
        if (token1.isRegularIdentifier()) {
            if ("TRANSFORM".equalsIgnoreCase(token1.getContent())) {
                transform = true;
            } else if ("TRANSFORMS".equalsIgnoreCase(token1.getContent())) {
                transforms = true;
            } else {
                reset(markedIndex);
                return null;
            }
        } else {
            reset(markedIndex);
            return null;
        }
        TransformsToBeDropped transformsToBeDropped = parseTransformsToBeDropped();
        Token token2 = scan();
        if (!token2.isFor()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a FOR.");
        }
        SchemaQualifiedName schemaResolvedUserDefinedTypeName = parseSchemaQualifiedName();
        DropBehavior dropBehavior = parseDropBehavior();
        return new DropTransformStatement(
                token.getBeginIndex(),
                transform,
                transforms,
                transformsToBeDropped,
                schemaResolvedUserDefinedTypeName,
                dropBehavior
        );
    }

    // <transforms to be dropped> ::= ALL | <transform group element>
    private TransformsToBeDropped parseTransformsToBeDropped() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isAll()) {
            return new TransformsToBeDropped(token.getBeginIndex(), token.getEndIndex());
        }
        if (token.isRegularIdentifier()) {
            return new TransformsToBeDropped(new Identifier(token));
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to transforms to be dropped.");
    }

    // DROP ORDERING FOR <schema-resolved user-defined type name> <drop behavior>
    private DropUserDefinedOrderingStatement tryParseDropUserDefinedOrderingStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"ORDERING".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isFor()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a FOR.");
        }
        SchemaQualifiedName schemaResolvedUserDefinedTypeName = tryParseSchemaQualifiedName();
        DropBehavior dropBehavior = parseDropBehavior();
        return new DropUserDefinedOrderingStatement(
                token.getBeginIndex(),
                schemaResolvedUserDefinedTypeName,
                dropBehavior
        );
    }

    // DROP TYPE <schema-resolved user-defined type name> <drop behavior>
    private DropDataTypeStatement tryParseDropDataTypeStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"TYPE".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName schemaResolvedUserDefinedTypeName = parseSchemaQualifiedName();
        DropBehavior dropBehavior = parseDropBehavior();
        return new DropDataTypeStatement(
                token.getBeginIndex(),
                schemaResolvedUserDefinedTypeName,
                dropBehavior
        );
    }

    // DROP CHARACTER SET <character set name>
    private DropCharacterSetStatement tryParseDropCharacterSetStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isCharacter()) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isSet()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a SET.");
        }
        SchemaQualifiedName characterSetName = parseSchemaQualifiedName();
        return new DropCharacterSetStatement(token.getBeginIndex(), characterSetName);
    }

    // DROP COLLATION <collation name> <drop behavior>
    private DropCollationStatement tryParseDropCollationStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"ASSERTION".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName collationName = parseSchemaQualifiedName();
        DropBehavior dropBehavior = parseDropBehavior();
        return new DropCollationStatement(token.getBeginIndex(), collationName, dropBehavior);
    }

    // DROP ASSERTION <constraint name>
    private DropAssertionStatement tryParseDropAssertionStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"ASSERTION".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName constraintName = parseSchemaQualifiedName();
        return new DropAssertionStatement(token.getBeginIndex(), constraintName);
    }

    // DROP TRIGGER <trigger name>
    private DropTriggerStatement tryParseDropTriggerStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isTranslation()) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName triggerName = parseSchemaQualifiedName();
        return new DropTriggerStatement(token.getBeginIndex(), triggerName);
    }

    // DROP TRANSLATION <transliteration name>
    private DropTransliterationStatement tryParseDropTranslationStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isTranslation()) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName translationName = parseSchemaQualifiedName();
        return new DropTransliterationStatement(token.getBeginIndex(), translationName);
    }

    // DROP DOMAIN <domain name> <drop behavior>
    private DropDomainStatement tryParseDropDomainStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"DOMAIN".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName domainName = parseSchemaQualifiedName();
        DropBehavior dropBehavior = parseDropBehavior();
        return new DropDomainStatement(
                token.getBeginIndex(),
                domainName,
                dropBehavior
        );
    }

    // DROP CAST <left paren> <source data type> AS <target data type> <right paren> <drop behavior>
    private DropUserDefinedCastStatement tryParseDropUserDefinedCastStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isCast()) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a (.");
        }
        DataType sourceDataType = parseDataType();
        Token token3 = scan();
        if (!token3.isAs()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a AS.");
        }
        DataType targetDataType = parseDataType();
        Token token4 = scan();
        if (!token4.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a ).");
        }
        DropBehavior dropBehavior = parseDropBehavior();
        return new DropUserDefinedCastStatement(
                token.getBeginIndex(),
                sourceDataType,
                targetDataType,
                dropBehavior
        );
    }

    // DROP <specific routine designator> <drop behavior>
    private DropRoutineStatement tryParseDropRoutineStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        SpecificRoutineDesignator specificRoutineDesignator = tryParseSpecificRoutineDesignator();
        if (specificRoutineDesignator == null) {
            reset(markedIndex);
            return null;
        }
        DropBehavior dropBehavior = parseDropBehavior();
        return new DropRoutineStatement(
                token.getBeginIndex(),
                specificRoutineDesignator,
                dropBehavior
        );
    }

    // DROP ROLE <role name>
    private DropRoleStatement tryParseDropRoleStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"ROLE".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        Identifier roleName = parseIdentifier();
        return new DropRoleStatement(token.getBeginIndex(), roleName);
    }

    // DROP VIEW <table name> <drop behavior>
    private DropViewStatement tryParseDropViewStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"VIEW".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        LocalOrSchemaQualifiedName tableName = parseLocalOrSchemaQualifiedName();
        DropBehavior dropBehavior = parseDropBehavior();
        return new DropViewStatement(
                token.getBeginIndex(),
                tableName,
                dropBehavior
        );
    }

    // DROP TABLE <table name> <drop behavior>
    private DropTableStatement tryParseDropTableStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isTable()) {
            reset(markedIndex);
            return null;
        }
        LocalOrSchemaQualifiedName tableName = parseLocalOrSchemaQualifiedName();
        DropBehavior dropBehavior = parseDropBehavior();
        return new DropTableStatement(
                token.getBeginIndex(),
                tableName,
                dropBehavior
        );
    }

    // DROP SCHEMA <schema name> <drop behavior>
    private DropSchemaStatement tryParseDropSchemaStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        SchemaName schemaName = parseSchemaName();
        DropBehavior dropBehavior = parseDropBehavior();
        return new DropSchemaStatement(
                token.getBeginIndex(),
                schemaName,
                dropBehavior
        );
    }

    //   <open statement>
    // | <fetch statement>
    // | <close statement>
    // | <select statement: single row>
    // | <free locator statement>
    // | <hold locator statement>
    // | <SQL data change statement>
    private SqlDataStatement tryParseSqlDataStatement() {
        OpenStatement openStatement = tryParseOpenStatement();
        if (openStatement != null) {
            return openStatement;
        }
        FetchStatement fetchStatement = tryParseFetchStatement();
        if (fetchStatement != null) {
            return fetchStatement;
        }
        CloseStatement closeStatement = tryParseCloseStatement();
        if (closeStatement != null) {
            return closeStatement;
        }
        SelectStatementSingleRow selectStatementSingleRow
                = tryParseSelectStatementSingleRow();
        if (selectStatementSingleRow != null) {
            return selectStatementSingleRow;
        }
        FreeLocatorStatement freeLocatorStatement
                = tryParseFreeLocatorStatement();
        if (freeLocatorStatement != null) {
            return freeLocatorStatement;
        }
        HoldLocatorStatement holdLocatorStatement
                = tryParseHoldLocatorStatement();
        if (holdLocatorStatement != null) {
            return holdLocatorStatement;
        }
        SqlDataChangeStatement sqlDataChangeStatement
                = tryParseSqlDataChangeStatement();
        if (sqlDataChangeStatement != null) {
            return sqlDataChangeStatement;
        }
        return null;
    }

    // SELECT [ <set quantifier> ] <select list> INTO <select target list> <table expression>
    private SelectStatementSingleRow tryParseSelectStatementSingleRow() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSelect()) {
            reset(markedIndex);
            return null;
        }
        SetQuantifier setQuantifier = tryParseSetQuantifier();
        SelectList selectList = parseSelectList();
        Token token1 = scan();
        if (!token1.isInto()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a INTO.");
        }
        SelectTargetList selectTargetList = parseSelectTargetList();
        TableExpression tableExpression = parseTableExpression();
        return new SelectStatementSingleRow(
                token.getBeginIndex(),
                setQuantifier,
                selectList,
                selectTargetList,
                tableExpression
        );
    }

    // FREE LOCATOR <locator reference> [ { <comma> <locator reference> }... ]
    private FreeLocatorStatement tryParseFreeLocatorStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isFree()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"LOCATOR".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a LOCATOR.");
        }
        LocatorReferenceList locatorReferenceList = parseLocatorReferenceList();
        return new FreeLocatorStatement(token.getBeginIndex(), locatorReferenceList);
    }

    // HOLD LOCATOR <locator reference> [ { <comma> <locator reference> }... ]
    private HoldLocatorStatement tryParseHoldLocatorStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isHold()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"LOCATOR".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a LOCATOR.");
        }
        LocatorReferenceList locatorReferenceList = parseLocatorReferenceList();
        return new HoldLocatorStatement(token.getBeginIndex(), locatorReferenceList);
    }

    //   <delete statement: positioned>
    // | <delete statement: searched>
    // | <insert statement>
    // | <update statement: positioned>
    // | <update statement: searched>
    // | <merge statement>
    private SqlDataChangeStatement tryParseSqlDataChangeStatement() {
        DeleteStatement deleteStatement = tryParseDeleteStatement();
        if (deleteStatement != null) {
            return deleteStatement;
        }
        InsertStatement insertStatement = tryParseInsertStatement();
        if (insertStatement != null) {
            return insertStatement;
        }
        UpdateStatement updateStatement = tryParseUpdateStatement();
        if (updateStatement != null) {
            return updateStatement;
        }
        MergeStatement mergeStatement = tryParseMergeStatement();
        if (mergeStatement != null) {
            return mergeStatement;
        }
        return null;
    }

    private LocatorReferenceList parseLocatorReferenceList() {
        LocatorReference locatorReference = parseLocatorReference();
        List<LocatorReference> locatorReferenceList = new ArrayList<>();
        locatorReferenceList.add(locatorReference);
        while (true) {
            int markedIndex2 = mark();
            Token token2 = scan();
            if (!token2.isComma()) {
                reset(markedIndex2);
                break;
            }
            LocatorReference locatorReference1 = parseLocatorReference();
            locatorReferenceList.add(locatorReference1);
        }
        return new LocatorReferenceList(locatorReferenceList);
    }

    // <locator reference>
    // ::= <host parameter name>
    //   | <embedded variable name>
    private LocatorReference parseLocatorReference() {
        HostParameterName hostParameterName = tryParseHostParameterName();
        if (hostParameterName != null) {
            return hostParameterName;
        }
        EmbeddedVariableName embeddedVariableName = tryParseEmbeddedVariableName();
        if (embeddedVariableName != null) {
            return embeddedVariableName;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to locator reference.");
    }

    // <target specification> [ { <comma> <target specification> }... ]
    private SelectTargetList parseSelectTargetList() {
        TargetSpecification targetSpecification = parseTargetSpecification();
        List<TargetSpecification> selectTargetList = new ArrayList<>();
        selectTargetList.add(targetSpecification);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isComma()) {
                reset(markedIndex);
                break;
            }
            TargetSpecification targetSpecification1 = parseTargetSpecification();
            selectTargetList.add(targetSpecification1);
        }
        return new SelectTargetList(selectTargetList);
    }

    //   <call statement>
    // | <return statement>
    private SqlControlStatement tryParseSqlControlStatement() {
        CallStatement callStatement = tryParseCallStatement();
        if (callStatement != null) {
            return callStatement;
        }
        ReturnStatement returnStatement = tryParseReturnStatement();
        if (returnStatement != null) {
            return returnStatement;
        }
        return null;
    }

    // CALL <routine invocation>
    private CallStatement tryParseCallStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCall()) {
            reset(markedIndex);
            return null;
        }
        RoutineInvocation routineInvocation = parseRoutineInvocation();
        return new CallStatement(token.getBeginIndex(), routineInvocation);
    }

    // RETURN <return value>
    private ReturnStatement tryParseReturnStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isReturn()) {
            reset(markedIndex);
            return null;
        }
        ReturnValue returnValue = parseReturnValue();
        return new ReturnStatement(token.getBeginIndex(), returnValue);
    }

    // <value expression> | NULL
    private ReturnValue parseReturnValue() {
        ValueExpression valueExpression = tryParseValueExpression();
        if (valueExpression != null) {
            return new ReturnValue(valueExpression);
        }
        Token token = scan();
        if (token.isNull()) {
            return new ReturnValue(token.getBeginIndex(), token.getEndIndex());
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to return value.");
    }

    //   <start transaction statement>
    // | <set transaction statement>
    // | <set constraints mode statement>
    // | <savepoint statement>
    // | <release savepoint statement>
    // | <commit statement>
    // | <rollback statement>
    private SqlTransactionStatement tryParseSqlTransactionStatement() {
        StartTransactionStatement startTransactionStatement
                = tryParseStartTransactionStatement();
        if (startTransactionStatement != null) {
            return startTransactionStatement;
        }
        SetTransactionStatement setTransactionStatement
                = tryParseSetTransactionStatement();
        if (setTransactionStatement != null) {
            return setTransactionStatement;
        }
        SetConstraintsModeStatement setConstraintsModeStatement
                = tryParseSetConstraintsModeStatement();
        if (setConstraintsModeStatement != null) {
            return setConstraintsModeStatement;
        }
        SavepointStatement savepointStatement = tryParseSavepointStatement();
        if (savepointStatement != null) {
            return savepointStatement;
        }
        ReleaseSavepointStatement releaseSavepointStatement
                = tryParseReleaseSavepointStatement();
        if (releaseSavepointStatement != null) {
            return releaseSavepointStatement;
        }
        CommitStatement commitStatement = tryParseCommitStatement();
        if (commitStatement != null) {
            return commitStatement;
        }
        RollbackStatement rollbackStatement = tryParseRollbackStatement();
        if (rollbackStatement != null) {
            return rollbackStatement;
        }
        return null;
    }

    private RollbackStatement parseRollbackStatement() {
        RollbackStatement rollbackStatement = tryParseRollbackStatement();
        if (rollbackStatement != null) {
            return rollbackStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to rollback statement.");
    }

    private CommitStatement parseCommitStatement() {
        CommitStatement commitStatement = tryParseCommitStatement();
        if (commitStatement != null) {
            return commitStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to commit statement.");
    }

    // COMMIT [ WORK ] [ AND [ NO ] CHAIN ]
    private CommitStatement tryParseCommitStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCommit()) {
            reset(markedIndex);
            return null;
        }
        int endIndex = token.getEndIndex();
        boolean work;
        boolean andChain;
        boolean andNoChain;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isRegularIdentifier() && "WORK".equalsIgnoreCase(token1.getContent())) {
            work = true;
            endIndex = token1.getEndIndex();
        } else {
            reset(markedIndex1);
            work = false;
        }
        int markedIndex2 = mark();
        Token token2 = scan();
        if (token2.isAnd()) {
            Token token3 = scan();
            if (token3.isNo()) {
                andChain = false;
                andNoChain = true;
                token3 = scan();
            } else {
                andChain = true;
                andNoChain = false;
            }
            if (!token3.isRegularIdentifier() || !"CHAIN".equalsIgnoreCase(token3.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a CHAIN.");
            }
            endIndex = token3.getEndIndex();
        } else {
            reset(markedIndex2);
            andChain = false;
            andNoChain = false;
        }
        return new CommitStatement(token.getBeginIndex(), endIndex, work, andChain, andNoChain);
    }

    // RELEASE SAVEPOINT <savepoint specifier>
    private ReleaseSavepointStatement tryParseReleaseSavepointStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRelease()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isSavepoint()) {
            reset(markedIndex);
            return null;
        }
        Identifier savepointSpecifier = parseIdentifier();
        return new ReleaseSavepointStatement(token.getBeginIndex(), savepointSpecifier);
    }

    private SetConstraintsModeStatement parseSetConstraintsModeStatement() {
        SetConstraintsModeStatement setConstraintsModeStatement
                = tryParseSetConstraintsModeStatement();
        if (setConstraintsModeStatement != null) {
            return setConstraintsModeStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to set constraints mode statement.");
    }

    // <set constraints mode statement>
    // ::= SET CONSTRAINTS <constraint name list> { DEFERRED | IMMEDIATE }
    private SetConstraintsModeStatement tryParseSetConstraintsModeStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        int endIndex;
        List<SchemaQualifiedName> constraintNameList;
        int markedIndex2 = mark();
        Token token2 = scan();
        if (token2.isAll()) {
            endIndex = token2.getEndIndex();
            constraintNameList = null;
        } else {
            reset(markedIndex2);
            SchemaQualifiedName constraintName = parseSchemaQualifiedName();
            constraintNameList = new ArrayList<>();
            constraintNameList.add(constraintName);
            while (true) {
                int markedIndex3 = mark();
                Token token3 = scan();
                if (token3.isComma()) {
                    reset(markedIndex3);
                    break;
                }
                SchemaQualifiedName constraintName1 = parseSchemaQualifiedName();
                constraintNameList.add(constraintName1);
            }
            endIndex = constraintNameList.get(constraintNameList.size() - 1).getEndIndex();
        }
        boolean deferred = false;
        boolean immediate = false;
        int markedIndex3 = mark();
        Token token3 = scan();
        if (token3.isRegularIdentifier() && "DEFERRED".equalsIgnoreCase(token3.getContent())) {
            deferred = true;
        } else if (token3.isImmediate()) {
            immediate = true;
        } else {
            reset(markedIndex3);
        }
        return new SetConstraintsModeStatement(
                token.getBeginIndex(),
                endIndex,
                constraintNameList == null ? null : new ConstraintNameList(token2.getBeginIndex(), endIndex, constraintNameList),
                deferred,
                immediate
        );
    }

    private SetTransactionStatement parseSetTransactionStatement() {
        SetTransactionStatement setTransactionStatement = tryParseSetTransactionStatement();
        if (setTransactionStatement != null) {
            return setTransactionStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to set transaction statement.");
    }

    // <set transaction statement> ::= SET [ LOCAL ] <transaction characteristics>
    private SetTransactionStatement tryParseSetTransactionStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            reset(markedIndex);
            return null;
        }
        boolean local = false;
        Token token1 = scan();
        if (token1.isRegularIdentifier() && "LOCAL".equalsIgnoreCase(token1.getContent())) {
            local = true;
            token1 = scan();
        }
        TransactionCharacteristics transactionCharacteristics = tryParseTransactionCharacteristics();
        if (transactionCharacteristics == null) {
            if (local == true) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Cannot resolve to set transaction statement.");
            }
            reset(markedIndex);
            return null;
        }
        return new SetTransactionStatement(token.getBeginIndex(), local, transactionCharacteristics);
    }

    private TransactionCharacteristics parseTransactionCharacteristics() {
        TransactionCharacteristics transactionCharacteristics = tryParseTransactionCharacteristics();
        if (transactionCharacteristics != null) {
            return transactionCharacteristics;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to transaction characteristics.");
    }

    // <transaction characteristics>
    // := TRANSACTION <transaction mode> [ { <comma> <transaction mode> }... ]
    private TransactionCharacteristics tryParseTransactionCharacteristics() {
        int markedIndex = mark();
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"TRANSACTION".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        TransactionMode transactionMode = parseTransactionMode();
        List<TransactionMode> transactionModeList = new ArrayList<>();
        transactionModeList.add(transactionMode);
        while (true) {
            int markedIndex3 = mark();
            Token token3 = scan();
            if (!token3.isComma()) {
                reset(markedIndex3);
                break;
            }
            TransactionMode transactionMode1 = parseTransactionMode();
            transactionModeList.add(transactionMode1);
        }
        return new TransactionCharacteristics(
                token1.getBeginIndex(),
                transactionModeList
        );
    }
    
    // <start transaction statement>
    // ::= START TRANSACTION
    //     [ <transaction mode> [ { <comma> <transaction mode> }... ] ]
    private StartTransactionStatement tryParseStartTransactionStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isStart()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"TRANSACTION".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        int endIndex = token1.getEndIndex();
        List<TransactionMode> transactionModeList;
        TransactionMode transactionMode = tryParseTransactionMode();
        if (transactionMode != null) {
            transactionModeList = new ArrayList<>();
            transactionModeList.add(transactionMode);
            while (true) {
                int markedIndex2 = mark();
                Token token2 = scan();
                if (!token2.isComma()) {
                    reset(markedIndex2);
                    break;
                }
                TransactionMode transactionMode1 = tryParseTransactionMode();
                transactionModeList.add(transactionMode1);
            }
            endIndex = transactionModeList.get(transactionModeList.size() - 1).getEndIndex();
        } else {
            transactionModeList = null;
        }
        return new StartTransactionStatement(
                token.getBeginIndex(),
                endIndex,
                transactionModeList
        );
    }

    //   <connect statement>
    // | <set connection statement>
    // | <disconnect statement>
    private SqlConnectionStatement tryParseSqlConnectionStatement() {
        ConnectStatement connectStatement = tryParseConnectStatement();
        if (connectStatement != null) {
            return connectStatement;
        }
        SetConnectionStatement setConnectionStatement
                = tryParseSetConnectionStatement();
        if (setConnectionStatement != null) {
            return setConnectionStatement;
        }
        DisconnectStatement disconnectStatement
                = tryParseDisconnectStatement();
        if (disconnectStatement != null) {
            return disconnectStatement;
        }
        return null;
    }

    // DISCONNECT <disconnect object>
    private DisconnectStatement tryParseDisconnectStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDisconnect()) {
            reset(markedIndex);
            return null;
        }
        DisconnectObject disconnectObject = parseDisconnectObject();
        return new DisconnectStatement(
                token.getBeginIndex(),
                disconnectObject
        );
    }

    // <connection object> | ALL | CURRENT
    private DisconnectObject parseDisconnectObject() {
        ConnectionObject connectionObject = tryParseConnectionObject();
        if (connectionObject != null) {
            return new DisconnectObject(connectionObject);
        }
        int markedIndex = mark();
        Token token = scan();
        if (token.isAll()) {
            return new DisconnectObject(
                    token.getBeginIndex(),
                    token.getEndIndex(),
                    true,
                    false
            );
        }
        if (token.isCurrent()) {
            return new DisconnectObject(
                    token.getBeginIndex(),
                    token.getEndIndex(),
                    false,
                    true
            );
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to disconnect object.");
    }

    private ConnectStatement parseConnectStatement() {
        ConnectStatement connectStatement = tryParseConnectStatement();
        if (connectStatement != null) {
            return connectStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to connect statement.");
    }

    // CONNECT TO <connection target>
    private ConnectStatement tryParseConnectStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isConnect()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isTo()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a TO.");
        }
        int markedIndex2 = mark();
        Token token2 = scan();
        if (token2.isDefault()) {
            return new ConnectStatement(token.getBeginIndex(), token2.getEndIndex());
        }
        reset(markedIndex2);
        SimpleValueSpecification sqlServerName = tryParseSimpleValueSpecification();
        if (sqlServerName == null) {
            Token token3 = scan();
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a SQL server name.");
        }
        int endIndex = sqlServerName.getEndIndex();
        SimpleValueSpecification connectionName;
        int markedIndex3 = mark();
        Token token3 = scan();
        if (token3.isAs()) {
            connectionName = tryParseSimpleValueSpecification();
            if (connectionName == null) {
                Token token4 = scan();
                throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a connection name.");
            }
            endIndex = connectionName.getEndIndex();
        } else {
            reset(markedIndex3);
            connectionName = null;
        }
        SimpleValueSpecification connectionUserName;
        int markedIndex4 = mark();
        Token token4 = scan();
        if (token4.isUser()) {
            connectionUserName = tryParseSimpleValueSpecification();
            if (connectionUserName == null) {
                Token token5 = scan();
                throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing a connection user name.");
            }
            endIndex = connectionUserName.getEndIndex();
        } else {
            reset(markedIndex4);
            connectionUserName = null;
        }
        return new ConnectStatement(token.getBeginIndex(), endIndex, sqlServerName, connectionName, connectionUserName);
    }

    //   <set session user identifier statement>
    // | <set role statement>
    // | <set local time zone statement>
    // | <set session characteristics statement>
    // | <set catalog statement>
    // | <set schema statement>
    // | <set names statement>
    // | <set path statement>
    // | <set transform group statement>
    // | <set session collation statement>
    private SqlSessionStatement tryParseSqlSessionStatement() {
        SetSessionUserIdentifierStatement setSessionUserIdentifierStatement
                = tryParseSetSessionUserIdentifierStatement();
        if (setSessionUserIdentifierStatement != null) {
            return setSessionUserIdentifierStatement;
        }
        SetRoleStatement setRoleStatement = tryParseSetRoleStatement();
        if (setRoleStatement != null) {
            return setRoleStatement;
        }
        SetLocalTimeZoneStatement setLocalTimeZoneStatement
                = tryParseSetLocalTimeZoneStatement();
        if (setLocalTimeZoneStatement != null) {
            return setLocalTimeZoneStatement;
        }
        SetSessionCharacteristicsStatement setSessionCharacteristicsStatement
                = tryParseSetSessionCharacteristicsStatement();
        if (setSessionCharacteristicsStatement != null) {
            return setSessionCharacteristicsStatement;
        }
        SetCatalogStatement setCatalogStatement = tryParseSetCatalogStatement();
        if (setCatalogStatement != null) {
            return setCatalogStatement;
        }
        SetSchemaStatement setSchemaStatement = tryParseSetSchemaStatement();
        if (setSchemaStatement != null) {
            return setSchemaStatement;
        }
        SetNamesStatement setNamesStatement = tryParseSetNamesStatement();
        if (setNamesStatement != null) {
            return setNamesStatement;
        }
        SetPathStatement setPathStatement = tryParseSetPathStatement();
        if (setPathStatement != null) {
            return setPathStatement;
        }
        SetTransformGroupStatement setTransformGroupStatement
                = tryParseSetTransformGroupStatement();
        if (setTransformGroupStatement != null) {
            return setTransformGroupStatement;
        }
        SetSessionCollationStatement setSessionCollationStatement
                = tryParseSetSessionCollationStatement();
        if (setSessionCollationStatement != null) {
            return setSessionCollationStatement;
        }
        return null;
    }

    // <set session collation statement>
    // ::= SET COLLATION <collation specification> [ FOR <character set specification list> ]
    //   | SET NO COLLATION [ FOR <character set specification list> ]
    private SetSessionCollationStatement tryParseSetSessionCollationStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (token1.isRegularIdentifier() && "COLLATION".equalsIgnoreCase(token1.getContent())) {
            ValueSpecification collationSpecification = parseValueSpecification();
            CharacterSetSpecificationList characterSetSpecificationList;
            int markedIndex2 = mark();
            Token token2 = scan();
            if (token2.isFor()) {
                characterSetSpecificationList = parseCharacterSetSpecificationList();
            } else {
                characterSetSpecificationList = null;
                reset(markedIndex2);
            }
            return new SetSessionCollationStatementSubtype1(
                    token.getBeginIndex(),
                    collationSpecification,
                    characterSetSpecificationList
            );
        }
        if (token1.isNo()) {
            Token token2 = scan();
            if (token2.isRegularIdentifier() && "COLLATION".equalsIgnoreCase(token2.getContent())) {
                int endIndex = token2.getEndIndex();
                CharacterSetSpecificationList characterSetSpecificationList;
                int markedIndex3 = mark();
                Token token3 = scan();
                if (token3.isFor()) {
                    characterSetSpecificationList = parseCharacterSetSpecificationList();
                    endIndex = characterSetSpecificationList.getEndIndex();
                } else {
                    characterSetSpecificationList = null;
                    reset(markedIndex3);
                }
                return new SetSessionCollationStatementSubtype2(
                        token.getBeginIndex(),
                        endIndex,
                        characterSetSpecificationList
                );
            }
        }
        reset(markedIndex);
        return null;
    }

    private CharacterSetSpecificationList parseCharacterSetSpecificationList() {
        CharacterSetSpecificationList characterSetSpecificationList
                = tryParseCharacterSetSpecificationList();
        if (characterSetSpecificationList != null) {
            return characterSetSpecificationList;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to character set specification list.");
    }

    // <character set specification list>
    // ::= <character set specification> [ {<comma> <character set specification> }... ]
    private CharacterSetSpecificationList tryParseCharacterSetSpecificationList() {
        CharacterSetName characterSetSpecification
                = tryParseCharacterSetName();
        if (characterSetSpecification == null) {
            return null;
        }
        List<CharacterSetName> characterSetSpecificationList
                = new ArrayList<>();
        characterSetSpecificationList.add(characterSetSpecification);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isComma()) {
                reset(markedIndex);
                break;
            }
            CharacterSetName characterSetSpecification1
                    = parseCharacterSetName();
            characterSetSpecificationList.add(characterSetSpecification1);
        }
        return new CharacterSetSpecificationList(characterSetSpecificationList);
    }

    // <set transform group statement> ::= SET <transform group characteristic>
    private SetTransformGroupStatement tryParseSetTransformGroupStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            reset(markedIndex);
            return null;
        }
        TransformGroupCharacteristic transformGroupCharacteristic
                = tryParseTransformGroupCharacteristic();
        if (transformGroupCharacteristic == null) {
            reset(markedIndex);
            return null;
        }
        return new SetTransformGroupStatement(
                token.getBeginIndex(),
                transformGroupCharacteristic
        );
    }

    // <transform group characteristic>
    // ::= DEFAULT TRANSFORM GROUP <value specification>
    //   | TRANSFORM GROUP FOR TYPE <path-resolved user-defined type name> <value specification>
    private TransformGroupCharacteristic tryParseTransformGroupCharacteristic() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isDefault()) {
            Token token1 = scan();
            if (!token1.isRegularIdentifier() || !"TRANSFORM".equalsIgnoreCase(token1.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a TRANSFORM.");
            }
            Token token2 = scan();
            if (!token2.isGroup()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a GROUP.");
            }
            ValueSpecification valueSpecification = parseValueSpecification();
            return new TransformGroupCharacteristicSubtype1(
                    token.getBeginIndex(),
                    valueSpecification
            );
        }
        if (token.isRegularIdentifier() && "TRANSFORM".equalsIgnoreCase(token.getContent())) {
            Token token1 = scan();
            if (!token1.isGroup()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a GROUP.");
            }
            Token token2 = scan();
            if (!token2.isFor()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a FOR.");
            }
            Token token3 = scan();
            if (!token3.isRegularIdentifier() || !"TYPE".equalsIgnoreCase(token3.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a TYPE.");
            }
            SchemaQualifiedName pathResolvedUserDefinedTypeName = parseSchemaQualifiedName();
            ValueSpecification valueSpecification = parseValueSpecification();
            return new TransformGroupCharacteristicSubtype2(
                    token.getBeginIndex(),
                    pathResolvedUserDefinedTypeName,
                    valueSpecification
            );
        }
        reset(markedIndex);
        return null;
    }

    // <set path statement> ::= SET PATH <value specification>
    private SetPathStatement tryParseSetPathStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"PATH".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        ValueSpecification valueSpecification = parseValueSpecification();
        return new SetPathStatement(token.getBeginIndex(), valueSpecification);
    }

    private SetNamesStatement parseSetNamesStatement() {
        SetNamesStatement setNamesStatement = tryParseSetNamesStatement();
        if (setNamesStatement != null) {
            return setNamesStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to set names statement.");
    }

    // <set names statement> ::= SET NAMES <value specification>
    private SetNamesStatement tryParseSetNamesStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"NAMES".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        ValueSpecification valueSpecification = parseValueSpecification();
        return new SetNamesStatement(token.getBeginIndex(), valueSpecification);
    }

    private SetSchemaStatement parseSetSchemaStatement() {
        SetSchemaStatement setSchemaStatement = tryParseSetSchemaStatement();
        if (setSchemaStatement != null) {
            return setSchemaStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to set schema statement.");
    }

    // <set schema statement> ::= SET SCHEMA <value specification>
    private SetSchemaStatement tryParseSetSchemaStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"SCHEMA".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        ValueSpecification valueSpecification = parseValueSpecification();
        return new SetSchemaStatement(token.getBeginIndex(), valueSpecification);
    }

    private SetCatalogStatement parseSetCatalogStatement() {
        SetCatalogStatement setCatalogStatement = tryParseSetCatalogStatement();
        if (setCatalogStatement != null) {
            return setCatalogStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to set catalog statement.");
    }

    // SET CATALOG <value specification>
    private SetCatalogStatement tryParseSetCatalogStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"CATALOG".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        ValueSpecification valueSpecification = parseValueSpecification();
        return new SetCatalogStatement(token.getBeginIndex(), valueSpecification);
    }

    // <set session characteristics statement>
    // ::= SET SESSION CHARACTERISTICS AS <session characteristic list>
    private SetSessionCharacteristicsStatement tryParseSetSessionCharacteristicsStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"SESSION".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isRegularIdentifier() || !"CHARACTERISTICS".equalsIgnoreCase(token2.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token3 = scan();
        if (!token3.isAs()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a AS.");
        }
        SessionCharacteristicList sessionCharacteristicList = parseSessionCharacteristicList();
        return new SetSessionCharacteristicsStatement(
                token.getBeginIndex(),
                sessionCharacteristicList
        );
    }

    // <session characteristic list>
    // ::= <session characteristic> [ { <comma> <session characteristic> }... ]
    private SessionCharacteristicList parseSessionCharacteristicList() {
        TransactionCharacteristics sessionCharacteristic
                = parseTransactionCharacteristics();
        List<TransactionCharacteristics> sessionCharacteristicList
                = new ArrayList<>();
        sessionCharacteristicList.add(sessionCharacteristic);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isCommit()) {
                reset(markedIndex);
                break;
            }
            TransactionCharacteristics sessionCharacteristic1
                    = parseTransactionCharacteristics();
            sessionCharacteristicList.add(sessionCharacteristic1);
        }
        return new SessionCharacteristicList(sessionCharacteristicList);
    }

    private SetLocalTimeZoneStatement parseSetLocalTimeZoneStatement() {
        SetLocalTimeZoneStatement setLocalTimeZoneStatement
                = tryParseSetLocalTimeZoneStatement();
        if (setLocalTimeZoneStatement != null) {
            return setLocalTimeZoneStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to set local time zone statement.");
    }

    // <set local time zone statement> ::= SET TIME ZONE <set time zone value>
    private SetLocalTimeZoneStatement tryParseSetLocalTimeZoneStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isTime()) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isRegularIdentifier() || !"ZONE".equalsIgnoreCase(token2.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ZONE.");
        }
        SetTimeZoneValue setTimeZoneValue = parseSetTimeZoneValue();
        return new SetLocalTimeZoneStatement(token.getBeginIndex(), setTimeZoneValue);
    }

    // <set role statement> ::= SET ROLE <role specification>
    private SetRoleStatement tryParseSetRoleStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"ROLE".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        RoleSpecification roleSpecification = parseRoleSpecification();
        return new SetRoleStatement(token.getBeginIndex(), roleSpecification);
    }

    // <role specification> ::= <value specification> | NONE
    private RoleSpecification parseRoleSpecification() {
        ValueSpecification valueSpecification = tryParseValueSpecification();
        if (valueSpecification != null) {
            return new RoleSpecification(valueSpecification);
        }
        Token token = scan();
        if (!token.isNone()) {
            return new RoleSpecification(token.getBeginIndex(), token.getEndIndex());
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to role specification.");
    }

    private SetSessionUserIdentifierStatement parseSetSessionUserIdentifierStatement() {
        SetSessionUserIdentifierStatement setSessionUserIdentifierStatement
                = tryParseSetSessionUserIdentifierStatement();
        if (setSessionUserIdentifierStatement != null) {
            return setSessionUserIdentifierStatement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to set session user identifier statement.");
    }

    // SET SESSION AUTHORIZATION <value specification>
    private SetSessionUserIdentifierStatement tryParseSetSessionUserIdentifierStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"SESSION".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isAuthorization()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a AUTHORIZATION.");
        }
        ValueSpecification valueSpecification = parseValueSpecification();
        return new SetSessionUserIdentifierStatement(token.getBeginIndex(), valueSpecification);
    }

    //   <system descriptor statement>
    // | <prepare statement>
    // | <deallocate prepared statement>
    // | <describe statement>
    // | <execute statement>
    // | <execute immediate statement>
    // | <SQL dynamic data statement>
    private SqlDynamicStatement tryParseSqlDynamicStatement() {
        SystemDescriptorStatement systemDescriptorStatement
                = parseSystemDescriptorStatement();
        if (systemDescriptorStatement != null) {
            return systemDescriptorStatement;
        }
        PrepareStatement prepareStatement = tryParsePrepareStatement();
        if (prepareStatement != null) {
            return prepareStatement;
        }
        DeallocatePreparedStatement deallocatePreparedStatement
                = tryParseDeallocatePreparedStatement();
        if (deallocatePreparedStatement != null) {
            return deallocatePreparedStatement;
        }
        DescribeStatement describeStatement = tryParseDescribeStatement();
        if (describeStatement != null) {
            return describeStatement;
        }
        ExecuteStatement executeStatement = tryParseExecuteStatement();
        if (executeStatement != null) {
            return executeStatement;
        }
        ExecuteImmediateStatement executeImmediateStatement
                = tryParseExecuteImmediateStatement();
        if (executeImmediateStatement != null) {
            return executeImmediateStatement;
        }
        SqlDynamicDataStatement sqlDynamicDataStatement
                = tryParseSqlDynamicDataStatement();
        if (sqlDynamicDataStatement != null) {
            return sqlDynamicDataStatement;
        }
        return null;
    }

    //   <allocate cursor statement>
    // | <dynamic open statement>
    // | <dynamic fetch statement>
    // | <dynamic close statement>
    // | <dynamic delete statement: positioned>
    // | <dynamic update statement: positioned>
    private SqlDynamicDataStatement tryParseSqlDynamicDataStatement() {
        AllocateCursorStatement allocateCursorStatement
                = tryParseAllocateCursorStatement();
        if (allocateCursorStatement != null) {
            return allocateCursorStatement;
        }
        DynamicOpenStatement dynamicOpenStatement
                = tryParseDynamicOpenStatement();
        if (dynamicOpenStatement != null) {
            return dynamicOpenStatement;
        }
        DynamicFetchStatement dynamicFetchStatement
                = tryParseDynamicFetchStatement();
        if (dynamicFetchStatement != null) {
            return dynamicFetchStatement;
        }
        DynamicCloseStatement dynamicCloseStatement
                = tryParseDynamicCloseStatement();
        if (dynamicCloseStatement != null) {
            return dynamicCloseStatement;
        }
        DynamicDeleteStatementPositioned dynamicDeleteStatementPositioned
                = tryParseDynamicDeleteStatementPositioned();
        if (dynamicDeleteStatementPositioned != null) {
            return dynamicDeleteStatementPositioned;
        }
        DynamicUpdateStatementPositioned dynamicUpdateStatementPositioned
                = tryParseDynamicUpdateStatementPositioned();
        if (dynamicUpdateStatementPositioned != null) {
            return dynamicUpdateStatementPositioned;
        }
        return null;
    }

    // <dynamic open statement>
    // ::= OPEN <dynamic cursor name> [ <input using clause> ]
    private DynamicOpenStatement tryParseDynamicOpenStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isOpen()) {
            reset(markedIndex);
            return null;
        }
        DynamicCursorName dynamicCursorName = parseDynamicCursorName();
        InputUsingClause inputUsingClause = tryParseInputUsingClause();
        return new DynamicOpenStatement(
                token.getBeginIndex(),
                dynamicCursorName,
                inputUsingClause
        );
    }

    // <dynamic fetch statement>
    // ::= FETCH [ [ <fetch orientation> ] FROM ] <dynamic cursor name> <output using clause>
    private DynamicFetchStatement tryParseDynamicFetchStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isFetch()) {
            reset(markedIndex);
            return null;
        }
        FetchOrientation fetchOrientation = tryParseFetchOrientation();
        boolean from;
        if (fetchOrientation != null) {
            Token token1 = scan();
            if (!token1.isFrom()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a FROM.");
            }
            from = true;
        } else {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (token1.isFrom()) {
                from = true;
            } else {
                reset(markedIndex1);
                from = true;
            }
        }
        DynamicCursorName dynamicCursorName = parseDynamicCursorName();
        OutputUsingClause outputUsingClause = parseOutputUsingClause();
        return new DynamicFetchStatement(
                token.getBeginIndex(),
                fetchOrientation,
                from,
                dynamicCursorName,
                outputUsingClause
        );
    }

    // <dynamic close statement> ::= CLOSE <dynamic cursor name>
    private DynamicCloseStatement tryParseDynamicCloseStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isClose()) {
            reset(markedIndex);
            return null;
        }
        DynamicCursorName dynamicCursorName = parseDynamicCursorName();
        return new DynamicCloseStatement(token.getBeginIndex(), dynamicCursorName);
    }

    // <dynamic delete statement: positioned>
    // ::= DELETE FROM <target table> WHERE CURRENT OF <dynamic cursor name>
    private DynamicDeleteStatementPositioned tryParseDynamicDeleteStatementPositioned() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDelete()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isFrom()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a FROM.");
        }
        TargetTable targetTable = parseTargetTable();
        Token token2 = scan();
        if (!token2.isWhere()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a WHERE.");
        }
        Token token3 = scan();
        if (!token3.isCurrent()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a CURRENT.");
        }
        Token token4 = scan();
        if (!token4.isOf()) {
            throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a OF.");
        }
        DynamicCursorName dynamicCursorName = parseDynamicCursorName();
        return new DynamicDeleteStatementPositioned(
                token.getBeginIndex(),
                targetTable,
                dynamicCursorName
        );
    }

    // <dynamic update statement: positioned>
    // ::= UPDATE <target table> SET <set clause list> WHERE CURRENT OF <dynamic cursor name>
    private DynamicUpdateStatementPositioned tryParseDynamicUpdateStatementPositioned() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDelete()) {
            reset(markedIndex);
            return null;
        }
        TargetTable targetTable = parseTargetTable();
        Token token1 = scan();
        if (!token1.isSet()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SET.");
        }
        SetClauseList setClauseList = parseSetClauseList();
        Token token2 = scan();
        if (!token2.isWhere()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a WHERE.");
        }
        Token token3 = scan();
        if (!token3.isCurrent()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a CURRENT.");
        }
        Token token4 = scan();
        if (!token4.isOf()) {
            throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a OF.");
        }
        DynamicCursorName dynamicCursorName = parseDynamicCursorName();
        return new DynamicUpdateStatementPositioned(
                token.getBeginIndex(),
                targetTable,
                setClauseList,
                dynamicCursorName
        );
    }

    // <allocate cursor statement> ::= ALLOCATE <extended cursor name> <cursor intent>
    private AllocateCursorStatement tryParseAllocateCursorStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAllocate()) {
            reset(markedIndex);
            return null;
        }
        ExtendedCursorName extendedCursorName = parseExtendedCursorName();
        CursorIntent cursorIntent = parseCursorIntent();
        return new AllocateCursorStatement(
                token.getBeginIndex(),
                extendedCursorName,
                cursorIntent
        );
    }

    // <cursor intent> ::=  <statement cursor> | <result set cursor>
    private CursorIntent parseCursorIntent() {
        StatementCursor statementCursor = tryParseStatementCursor();
        if (statementCursor != null) {
            return statementCursor;
        }
        ResultSetCursor resultSetCursor = tryParseResultSetCursor();
        if (resultSetCursor != null) {
            return resultSetCursor;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to cursor intent.");
    }

    // <statement cursor>
    // ::=
    // [ <cursor sensitivity> ]
    // [ <cursor scrollability> ]
    // CURSOR
    // [ <cursor holdability> ]
    // [ <cursor returnability> ]
    // FOR
    // <extended statement name>
    private StatementCursor tryParseStatementCursor() {
        CursorSensitivity cursorSensitivity = tryParseCursorSensitivity();
        CursorScrollability cursorScrollability = tryParseCursorScrollability();
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCursor()) {
            if (cursorSensitivity != null || cursorScrollability != null) {
                throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a CURSOR.");
            }
            reset(markedIndex);
            return null;
        }
        int beginIndex = token.getBeginIndex();
        if (cursorScrollability != null) {
            beginIndex = cursorScrollability.getBeginIndex();
        }
        if (cursorSensitivity != null) {
            beginIndex = cursorSensitivity.getBeginIndex();
        }
        CursorHoldability cursorHoldability = tryParseCursorHoldability();
        CursorReturnability cursorReturnability = tryParseCursorReturnability();
        Token token1 = scan();
        if (!token1.isFor()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a FOR.");
        }
        ExtendedStatementName extendedStatementName = parseExtendedStatementName();
        return new StatementCursor(
                beginIndex,
                cursorSensitivity,
                cursorScrollability,
                cursorHoldability,
                cursorReturnability,
                extendedStatementName
        );
    }

    // <result set cursor> ::= FOR PROCEDURE <specific routine designator>
    private ResultSetCursor tryParseResultSetCursor() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isFor()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isProcedure()) {
            reset(markedIndex);
            return null;
        }
        SpecificRoutineDesignator specificRoutineDesignator
                = parseSpecificRoutineDesignator();
        return new ResultSetCursor(
                token.getBeginIndex(),
                specificRoutineDesignator
        );
    }

    // EXECUTE IMMEDIATE <SQL statement variable>
    private ExecuteImmediateStatement tryParseExecuteImmediateStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isExecute()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isImmediate()) {
            reset(markedIndex);
            return null;
        }
        SimpleValueSpecification sqlStatementVariable
                = parseSimpleValueSpecification();
        return new ExecuteImmediateStatement(
                token.getBeginIndex(),
                sqlStatementVariable
        );
    }

    // DEALLOCATE PREPARE <SQL statement name>
    private DeallocatePreparedStatement tryParseDeallocatePreparedStatement() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDeallocate()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isPrepare()) {
            reset(markedIndex);
            return null;
        }
        SqlStatementName sqlStatementName = parseSqlStatementName();
        return new DeallocatePreparedStatement(
                token.getBeginIndex(),
                sqlStatementName
        );
    }

    private TriggerActionTime parseTriggerActionTime() {
        Token token = scan();
        if (token.isRegularIdentifier() && "BEFORE".equalsIgnoreCase(token.getContent())) {
            return new TriggerActionTime(token.getBeginIndex(), token.getEndIndex(), true, false);
        }
        if (token.isRegularIdentifier() && "AFTER".equalsIgnoreCase(token.getContent())) {
            return new TriggerActionTime(token.getBeginIndex(), token.getEndIndex(), false, true);
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a BEFORE or AFTER.");
    }

    // INSERT | DELETE | UPDATE [ OF <trigger column list> ]
    private TriggerEvent parseTriggerEvent() {
        Token token = scan();
        if (token.isInsert()) {
            return new TriggerEvent(token.getBeginIndex(), token.getEndIndex(), true, false, false, null);
        }
        if (token.isDelete()) {
            return new TriggerEvent(token.getBeginIndex(), token.getEndIndex(), false, true, false, null);
        }
        if (token.isUpdate()) {
            IdentifierList triggerColumnList = null;
            int markedIndex1 = mark();
            Token token1 = scan();
            if (token1.isOf()) {
                triggerColumnList = parseIdentifierList();
            } else {
                reset(markedIndex1);
            }
            return new TriggerEvent(token.getBeginIndex(), token.getEndIndex(), false, false, true, triggerColumnList);
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to trigger event.");
    }

    private TableDefinition tryParseTableDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCreate()) {
            reset(markedIndex);
            return null;
        }
        TableScope tableScope = tryParseTableScope();
        Token token1 = scan();
        if (!token1.isTable()) {
            reset(markedIndex);
            return null;
        }
        LocalOrSchemaQualifiedName tableName = parseLocalOrSchemaQualifiedName();
        TableContentsSource tableContentsSource = tryParseTableContentsSource();
        TableCommitAction tableCommitAction;
        int markedIndex2 = mark();
        Token token2 = scan();
        if (token2.isOn()) {
            Token token3 = scan();
            if (!token3.isCommit()) {
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a COMMIT.");
            }
            tableCommitAction = parseTableCommitAction();
            Token token4 = scan();
            if (!token4.isRows()) {
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ROWS.");
            }
        } else {
            reset(markedIndex2);
            tableCommitAction = null;
        }
        int endIndex;
        if (tableCommitAction != null) {
            endIndex = tableCommitAction.getEndIndex();
        } else {
            endIndex = tableContentsSource.getEndIndex();
        }
        return new TableDefinition(
                token.getBeginIndex(),
                endIndex,
                tableScope,
                tableName,
                tableContentsSource,
                tableCommitAction);
    }

    private TableScope tryParseTableScope() {
        boolean global;
        boolean local;
        int markedIndex = mark();
        Token token = scan();
        if (token.isGlobal()) {
            global = true;
            local = false;
        } else if (token.isLocal()) {
            global = false;
            local = true;
        } else {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"TEMPORARY".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a TEMPORARY.");
        }
        return new TableScope(token.getBeginIndex(), token1.getEndIndex(), global, local);
    }

    private TableContentsSource tryParseTableContentsSource() {
        TableElementList tableElementList = tryParseTableElementList();
        if (tableElementList != null) {
            return tableElementList;
        }
        OfPathResolvedUserDefinedTypeName ofPathResolvedUserDefinedTypeName
                = tryParseOfPathResolvedUserDefinedTypeName();
        if (ofPathResolvedUserDefinedTypeName != null) {
            return ofPathResolvedUserDefinedTypeName;
        }
        return tryParseAsSubqueryClause();
    }

    // <left paren> <table element> [ { <comma> <table element> }... ] <right paren>
    private TableElementList tryParseTableElementList() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        TableElement tableElement = parseTableElement();
        List<TableElement> tableElementList = new ArrayList<>();
        tableElementList.add(tableElement);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            TableElement tableElement1 = parseTableElement();
            tableElementList.add(tableElement1);
        }
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new TableElementList(token.getBeginIndex(), token1.getEndIndex(), tableElementList);
    }

    //   <column definition>
    // | <table constraint definition>
    // | <like clause>
    // | <self-referencing column specification>
    // | <column options>
    private TableElement parseTableElement() {
        ColumnDefinition columnDefinition = tryParseColumnDefinition();
        if (columnDefinition != null) {
            return columnDefinition;
        }
        TableConstraintDefinition tableConstraintDefinition
                = tryParseTableConstraintDefinition();
        if (tableConstraintDefinition != null) {
            return tableConstraintDefinition;
        }
        LikeClause likeClause = tryParseLikeClause();
        if (likeClause != null) {
            return likeClause;
        }
        SelfReferencingColumnSpecification selfReferencingColumnSpecification
                = tryParseSelfReferencingColumnSpecification();
        if (selfReferencingColumnSpecification != null) {
            return selfReferencingColumnSpecification;
        }
        ColumnOptions columnOptions = tryParseColumnOptions();
        if (columnOptions != null) {
            return columnOptions;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to table element.");
    }

    // LIKE <table name> [ <like options> ]
    private LikeClause tryParseLikeClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLike()) {
            reset(markedIndex);
            return null;
        }
        LocalOrSchemaQualifiedName tableName = parseLocalOrSchemaQualifiedName();
        int endIndex = tableName.getEndIndex();
        LikeOptions likeOptions = tryParseLikeOptions();
        if (likeOptions != null) {
            endIndex = likeOptions.getEndIndex();
        }
        return new LikeClause(token.getBeginIndex(), endIndex, tableName, likeOptions);
    }

    // <identity option> | <column default option>
    private LikeOptions tryParseLikeOptions() {
        IdentityOption identityOption = tryParseIdentityOption();
        if (identityOption != null) {
            return identityOption;
        }
        ColumnDefaultOption columnDefaultOption = tryParseColumnDefaultOption();
        if (columnDefaultOption != null) {
            return columnDefaultOption;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to like options.");
    }

    // INCLUDING IDENTITY | EXCLUDING IDENTITY
    private IdentityOption tryParseIdentityOption() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "INCLUDING".equalsIgnoreCase(token.getContent())) {
            Token token1 = scan();
            if (!token1.isIdentity()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a IDENTITY.");
            }
            return new IdentityOption(
                    token.getBeginIndex(),
                    token.getEndIndex(),
                    true,
                    false);
        }
        if (token.isRegularIdentifier() && "EXCLUDING".equalsIgnoreCase(token.getContent())) {
            Token token1 = scan();
            if (!token1.isIdentity()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a IDENTITY.");
            }
            return new IdentityOption(
                    token.getBeginIndex(),
                    token.getEndIndex(),
                    false,
                    true);
        }
        reset(markedIndex);
        return null;
    }

    // INCLUDING DEFAULTS | EXCLUDING DEFAULTS
    private ColumnDefaultOption tryParseColumnDefaultOption() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "INCLUDING".equalsIgnoreCase(token.getContent())) {
            Token token1 = scan();
            if (!token1.isRegularIdentifier() || !"DEFAULTS".equalsIgnoreCase(token1.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a DEFAULTS.");
            }
            return new ColumnDefaultOption(
                    token.getBeginIndex(),
                    token.getEndIndex(),
                    true,
                    false);
        }
        if (token.isRegularIdentifier() && "EXCLUDING".equalsIgnoreCase(token.getContent())) {
            Token token1 = scan();
            if (!token1.isRegularIdentifier() || !"DEFAULTS".equalsIgnoreCase(token1.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a DEFAULTS.");
            }
            return new ColumnDefaultOption(
                    token.getBeginIndex(),
                    token.getEndIndex(),
                    false,
                    true);
        }
        reset(markedIndex);
        return null;
    }

    // <column name> WITH OPTIONS <column option list>
    private ColumnOptions tryParseColumnOptions() {
        Identifier columnName = tryParseIdentifier();
        if (columnName == null) {
            return null;
        }
        Token token = scan();
        if (!token.isWith()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a WITH.");
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"OPTIONS".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a OPTIONS.");
        }
        int endIndex = token1.getEndIndex();
        ColumnOptionList columnOptionList = tryParseColumnOptionList();
        if (columnOptionList != null) {
            endIndex = columnOptionList.getEndIndex();
        }
        return new ColumnOptions(endIndex, columnName, columnOptionList);
    }

    // [ <scope clause> ] [ <default clause> ] [ <column constraint definition> ... ]
    private ColumnOptionList tryParseColumnOptionList() {
        int markedIndex = mark();
        Token token = scan();
        reset(markedIndex);
        ScopeClause scopeClause = tryParseScopeClause();
        DefaultClause defaultClause = tryParseDefaultClause();
        List<ColumnConstraintDefinition> columnConstraintDefinitionList = null;
        while (true) {
            ColumnConstraintDefinition columnConstraintDefinition = tryParseColumnConstraintDefinition();
            if (columnConstraintDefinition == null) {
                break;
            }
            if (columnConstraintDefinitionList == null) {
                columnConstraintDefinitionList = new ArrayList<>();
            }
            columnConstraintDefinitionList.add(columnConstraintDefinition);
        }
        if (scopeClause == null && defaultClause == null && columnConstraintDefinitionList == null) {
            return null;
        }
        int endIndex = token.getEndIndex();
        if (scopeClause != null) {
            endIndex = scopeClause.getEndIndex();
        }
        if (defaultClause != null) {
            endIndex = defaultClause.getEndIndex();
        }
        if (columnConstraintDefinitionList != null) {
            endIndex = columnConstraintDefinitionList.get(columnConstraintDefinitionList.size() - 1).getEndIndex();
        }
        return new ColumnOptionList(
                token.getBeginIndex(),
                endIndex,
                scopeClause,
                defaultClause,
                columnConstraintDefinitionList == null ? null : new ColumnConstraintDefinitionList(columnConstraintDefinitionList));
    }

    // OF <path-resolved user-defined type name> [ <subtable clause> ] [ <table element list> ]
    private OfPathResolvedUserDefinedTypeName tryParseOfPathResolvedUserDefinedTypeName() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isOf()) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName pathResolvedUserDefinedTypeName = parseSchemaQualifiedName();
        SubtableClause subtableClause = tryParseSubtableClause();
        TableElementList tableElementList = tryParseTableElementList();
        int endIndex = pathResolvedUserDefinedTypeName.getEndIndex();
        if (subtableClause != null) {
            endIndex = subtableClause.getEndIndex();
        }
        if (tableElementList != null) {
            endIndex = tableElementList.getEndIndex();
        }
        return new OfPathResolvedUserDefinedTypeName(
                token.getBeginIndex(),
                endIndex,
                pathResolvedUserDefinedTypeName,
                subtableClause,
                tableElementList
        );
    }

    // UNDER <supertable clause>
    private SubtableClause tryParseSubtableClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"UNDER".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        LocalOrSchemaQualifiedName tableName = parseLocalOrSchemaQualifiedName();
        return new SubtableClause(token.getBeginIndex(), tableName);
    }

    // [ <left paren> <column name list> <right paren> ] AS <subquery> <with or without data>
    private AsSubqueryClause tryParseAsSubqueryClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen() && !token.isAs()) {
            reset(markedIndex);
            return null;
        }
        int beginIndex = token.getBeginIndex();
        IdentifierList columnNameList = null;
        if (token.isLeftParen()) {
            columnNameList = parseIdentifierList();
            Token token1 = scan();
            if (!token1.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
            }
            token = scan();
        }
        if (!token.isAs()) {
            reset(markedIndex);
            return null;
        }
        Subquery subquery = parseSubquery();
        WithOrWithoutData withOrWithoutData = parseWithOrWithoutData();
        return new AsSubqueryClause(
                beginIndex,
                withOrWithoutData.getEndIndex(),
                columnNameList,
                subquery,
                withOrWithoutData);
    }

    // WITH NO DATA | WITH DATA
    private WithOrWithoutData parseWithOrWithoutData() {
        Token token = scan();
        if (!token.isWith()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a WITH.");
        }
        boolean no = false;
        Token token1 = scan();
        if (token1.isNo()) {
            no = true;
            token1 = scan();
        }
        if (!token1.isRegularIdentifier() || !"DATA".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a DATA.");
        }
        return new WithOrWithoutData(token.getBeginIndex(), token1.getEndIndex(), no, !no);
    }

    private TableCommitAction parseTableCommitAction() {
        TableCommitAction tableCommitAction = tryParseTableCommitAction();
        if (tableCommitAction != null) {
            return tableCommitAction;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to table commit action.");
    }

    private TableCommitAction tryParseTableCommitAction() {
        boolean preserve;
        boolean delete;
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"PRESERVE".equalsIgnoreCase(token.getContent())) {
            preserve = true;
            delete = false;
        } else if (token.isDelete()) {
            preserve = false;
            delete = true;
        } else {
            reset(markedIndex);
            return null;
        }
        return new TableCommitAction(token.getBeginIndex(), token.getEndIndex(), preserve, delete);
    }

    // CREATE CHARACTER SET <character set name> [ AS ] <character set source> [ <collate clause> ]
    private CharacterSetDefinition tryParseCharacterSetDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCreate()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isCharacter()) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isSet()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a SET.");
        }
        CharacterSetName characterSetName = parseCharacterSetName();
        int markedIndex3 = mark();
        Token token3 = scan();
        if (!token3.isAs()) {
            reset(markedIndex3);
        }
        CharacterSetSource characterSetSource = parseCharacterSetSource();
        int endIndex = characterSetSource.getEndIndex();
        CollateClause collateClause = tryParseCollateClause();
        if (collateClause != null) {
            endIndex = collateClause.getEndIndex();
        }
        return new CharacterSetDefinition(
                token.getBeginIndex(),
                endIndex,
                characterSetName,
                characterSetSource,
                collateClause);
    }

    // GET <character set specification>
    private CharacterSetSource parseCharacterSetSource() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isGet()) {
            reset(markedIndex);
            return null;
        }
        CharacterSetName characterSetSpecification = parseCharacterSetName();
        return new CharacterSetSource(token.getBeginIndex(), characterSetSpecification);
    }

    private SimpleValueSpecification parseSimpleValueSpecification() {
        SimpleValueSpecification simpleValueSpecification = tryParseSimpleValueSpecification();
        if (simpleValueSpecification != null) {
            return simpleValueSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to simple value specification.");
    }

    private SimpleValueSpecification tryParseSimpleValueSpecification() {
        SimpleValueSpecification simpleValueSpecification = tryParseLiteral();
        if (simpleValueSpecification != null) {
            return simpleValueSpecification;
        }
        int markedIndex = mark();
        Token token = scan();
        if (token.isColon()) {
            Token token1 = scan();
            if (token1.isRegularIdentifier() || token1.isUnicodeDelimiterBody()) {
                reset(markedIndex);
                simpleValueSpecification = tryParseHostParameterName();
            } else {
                throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Embedded variable name is not supported.");
            }
        } else {
            reset(markedIndex);
            simpleValueSpecification = tryParseIdentifierChain();
        }
        return simpleValueSpecification;
    }

    private IdentifierChain tryParseIdentifierChain() {
        Identifier identifier = tryParseIdentifier();
        if (identifier == null) {
            return null;
        }
        List<Identifier> identifierList = new ArrayList<>();
        identifierList.add(identifier);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isPeriod()) {
                reset(markedIndex);
                break;
            }
            Identifier identifier1 = parseIdentifier();
            identifierList.add(identifier1);
        }
        return new IdentifierChain(identifierList);
    }

    private IdentifierList parseIdentifierList() {
        IdentifierList identifierList = tryParseIdentifierList();
        if (identifierList != null) {
            return identifierList;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to identifier list.");
    }

    private IdentifierList tryParseIdentifierList() {
        Identifier identifier = tryParseIdentifier();
        if (identifier == null) {
            return null;
        }
        List<Identifier> identifierList = new ArrayList<>();
        identifierList.add(identifier);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isComma()) {
                reset(markedIndex);
                break;
            }
            Identifier identifier1 = parseIdentifier();
            identifierList.add(identifier1);
        }
        return new IdentifierList(identifierList);
    }

    private HostParameterName parseHostParameterName() {
        HostParameterName hostParameterName = tryParseHostParameterName();
        if (hostParameterName != null) {
            return hostParameterName;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to host parameter name.");
    }

    private HostParameterName tryParseHostParameterName() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isColon()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() && !token1.isUnicodeDelimiterBody()) {
            reset(markedIndex);
            return null;
        }
        return new HostParameterName(token.getBeginIndex(), new Identifier(token1));
    }

    private LocalQualifiedName parseLocalQualifiedName() {
        LocalQualifiedName localQualifiedName = tryParseLocalQualifiedName();
        if (localQualifiedName != null) {
            return localQualifiedName;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a MODULE or identifier.");
    }

    private LocalQualifiedName tryParseLocalQualifiedName() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isModule() && !token.isRegularIdentifier()) {
            reset(markedIndex);
            return null;
        }
        boolean module;
        Identifier qualifiedIdentifier;
        if (token.isModule()) {
            Token token1 = scan();
            if (!token1.isPeriod()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ..");
            }
            module = true;
            Token token2 = scan();
            if (!token2.isRegularIdentifier()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing an identifier.");
            }
            qualifiedIdentifier = new Identifier(token2);
        } else {
            module = false;
            qualifiedIdentifier = new Identifier(token);
        }
        return new LocalQualifiedName(token.getBeginIndex(), module, qualifiedIdentifier);
    }

    private LocalOrSchemaQualifiedName parseLocalOrSchemaQualifiedName() {
        LocalOrSchemaQualifiedName localOrSchemaQualifiedName = tryParseLocalOrSchemaQualifiedName();
        if (localOrSchemaQualifiedName != null) {
            return localOrSchemaQualifiedName;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to local or schema qualified name.");
    }

    private LocalOrSchemaQualifiedName tryParseLocalOrSchemaQualifiedName() {
        Token token1 = scan();
        if (!token1.isRegularIdentifier() && !token1.isUnicodeDelimiterBody() && !token1.isModule()) {
            return null;
        }
        if (token1.isModule()) {
            Token token2 = scan();
            if (!token2.isPeriod()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ..");
            }
            Token token3 = scan();
            if (!token3.isRegularIdentifier() && !token3.isUnicodeDelimiterBody()) {
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing an identifier.");
            }
            return new LocalOrSchemaQualifiedName(token1.getBeginIndex(), new Identifier(token3));
        }
        int markedIndex2 = mark();
        Token token2 = scan();
        if (!token2.isPeriod()) {
            reset(markedIndex2);
            return new LocalOrSchemaQualifiedName(null, new Identifier(token1));
        }
        Token token3 = scan();
        if (!token3.isRegularIdentifier() && !token3.isUnicodeDelimiterBody()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing an identifier.");
        }
        int markedIndex4 = mark();
        Token token4 = scan();
        if (!token4.isPeriod()) {
            reset(markedIndex4);
            return new LocalOrSchemaQualifiedName(new SchemaName(null, new Identifier(token1)), new Identifier(token3));
        }
        Token token5 = scan();
        if (!token5.isRegularIdentifier() && !token5.isUnicodeDelimiterBody()) {
            throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing an identifier.");
        }
        return new LocalOrSchemaQualifiedName(new SchemaName(new CatalogName(new Identifier(token1)), new Identifier(token3)), new Identifier(token5));
    }

    private SchemaQualifiedName parseSchemaQualifiedName() {
        SchemaQualifiedName schemaQualifiedName = tryParseSchemaQualifiedName();
        if (schemaQualifiedName != null) {
            return schemaQualifiedName;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to schema qualified name.");
    }

    private SchemaQualifiedName tryParseSchemaQualifiedName() {
        Token token1 = scan();
        if (!token1.isRegularIdentifier() && !token1.isUnicodeDelimiterBody()) {
            return null;
        }
        int markedIndex2 = mark();
        Token token2 = scan();
        if (!token2.isPeriod()) {
            reset(markedIndex2);
            return new SchemaQualifiedName(null, new Identifier(token1));
        }
        Token token3 = scan();
        if (!token3.isRegularIdentifier() && !token3.isUnicodeDelimiterBody()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing an identifier.");
        }
        int markedIndex4 = mark();
        Token token4 = scan();
        if (!token4.isPeriod()) {
            reset(markedIndex4);
            return new SchemaQualifiedName(new SchemaName(null, new Identifier(token1)), new Identifier(token3));
        }
        Token token5 = scan();
        if (!token5.isRegularIdentifier() && !token5.isUnicodeDelimiterBody()) {
            throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing an identifier.");
        }
        return new SchemaQualifiedName(new SchemaName(new CatalogName(new Identifier(token1)), new Identifier(token3)), new Identifier(token5));
    }

    private AlterTableAction parseAlterTableAction() {
        Token token = scan();
        if (token.isAdd()) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (token1.isColumn()) {
                ColumnDefinition columnDefinition = parseColumnDefinition();
                return new AddColumnDefinition(token.getBeginIndex(), columnDefinition);
            }
            reset(markedIndex1);
            ColumnDefinition columnDefinition = tryParseColumnDefinition();
            if (columnDefinition != null) {
                return new AddColumnDefinition(token.getBeginIndex(), columnDefinition);
            }
            TableConstraintDefinition tableConstraintDefinition = parseTableConstraintDefinition();
            return new AddTableConstraintDefinition(token.getBeginIndex(), tableConstraintDefinition);
        }
        if (token.isAlter()) {
            boolean column;
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isColumn()) {
                reset(markedIndex1);
                column = false;
            } else {
                column = true;
            }
            if (!token1.isRegularIdentifier() && !token1.isUnicodeDelimiterBody()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing an identifier.");
            }
            Identifier columnName = new Identifier(token1);
            AlterColumnAction alterColumnAction = parseAlterColumnAction();
            return new AlterColumnDefinition(token.getBeginIndex(), column, columnName, alterColumnAction);
        }
        if (token.isDrop()) {
            Token token1 = scan();
            if (token1.isColumn()) {
                token1 = scan();
                if (!token1.isRegularIdentifier() && !token1.isUnicodeDelimiterBody()) {
                    throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing an identifier.");
                }
                Identifier columnName = new Identifier(token1);
                DropBehavior dropBehavior = parseDropBehavior();
                return new DropColumnDefinition(token.getBeginIndex(), true, columnName, dropBehavior);
            }
            if (token1.isRegularIdentifier() || token1.isUnicodeDelimiterBody()) {
                Identifier columnName = new Identifier(token1);
                DropBehavior dropBehavior = parseDropBehavior();
                return new DropColumnDefinition(token.getBeginIndex(), false, columnName, dropBehavior);
            }
            if (token1.isConstraint()) {
                SchemaQualifiedName constraintName = parseSchemaQualifiedName();
                DropBehavior dropBehavior = parseDropBehavior();
                return new DropTableConstraintDefinition(token.getBeginIndex(), constraintName, dropBehavior);
            }
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Cannot resolve to drop statement.");
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to any statement.");
    }

    private ColumnDefinition tryParseColumnDefinition() {
        Token token1 = scan();
        if (!token1.isRegularIdentifier() && !token1.isUnicodeDelimiterBody()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing an identifier.");
        }
        Identifier columnName = new Identifier(token1);
        int endIndex = columnName.getEndIndex();
        DataType dataType = tryParseDataType();
        SchemaQualifiedName domainName = null;
        if (dataType == null) {
            domainName = parseSchemaQualifiedName();
            endIndex = domainName.getEndIndex();
        } else {
            endIndex = dataType.getEndIndex();
        }
        ReferenceScopeCheck referenceScopeCheck = tryParseReferenceScopeCheck();
        if (referenceScopeCheck != null) {
            endIndex = referenceScopeCheck.getEndIndex();
        }
        DefaultClause defaultClause = tryParseDefaultClause();
        IdentityColumnSpecification identityColumnSpecification = null;
        if (defaultClause == null) {
            identityColumnSpecification = tryParseIdentityColumnSpecification();
        } else {
            endIndex = defaultClause.getEndIndex();
        }
        GenerationClause generationClause = null;
        if (identityColumnSpecification == null) {
            generationClause = tryParseGenerationClause();
            if (generationClause != null) {
                endIndex = generationClause.getEndIndex();
            }
        } else {
            endIndex = identityColumnSpecification.getEndIndex();
        }
        List<ColumnConstraintDefinition> columnConstraintDefinitionList = null;
        while (true) {
            ColumnConstraintDefinition columnConstraintDefinition = tryParseColumnConstraintDefinition();
            if (columnConstraintDefinition == null) {
                break;
            }
            columnConstraintDefinitionList = new ArrayList<>();
            columnConstraintDefinitionList.add(columnConstraintDefinition);
            endIndex = columnConstraintDefinition.getEndIndex();
        }
        CollateClause collateClause = tryParseCollateClause();
        if (collateClause != null) {
            endIndex = collateClause.getEndIndex();
        }
        return new ColumnDefinition(
                columnName.getBeginIndex(),
                endIndex,
                columnName,
                dataType,
                domainName,
                referenceScopeCheck,
                defaultClause,
                identityColumnSpecification,
                generationClause,
                columnConstraintDefinitionList == null
                        ? null
                        :new ColumnConstraintDefinitionList(columnConstraintDefinitionList),
                collateClause);
    }

    private ColumnDefinition parseColumnDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() && !token.isUnicodeDelimiterBody()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing an identifier.");
        }
        reset(markedIndex);
        return tryParseColumnDefinition();
    }

    private DataType parseDataType() {
        DataType dataType = tryParseDataType();
        if (dataType != null) {
            return dataType;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to data type.");
    }

    private DataType tryParseDataType() {
        DataType dataType = tryParsePredefinedType();
        if (dataType == null) {
            dataType = tryParseRowType();
        }
        if (dataType == null) {
            dataType = tryParseSchemaQualifiedName();
        }
        if (dataType == null) {
            dataType = tryParseReferenceType();
        }
        if (dataType == null) {
            return null;
        }
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (token.isArray()) {
                int markedIndex1 = mark();
                Token token1 = scan();
                if (token1.isLeftBracket()) {
                    UnsignedInteger unsignedInteger = parseUnsignedInteger();
                    Token token2 = scan();
                    if (!token2.isRightBracket()) {
                        throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ].");
                    }
                    dataType = new ArrayType(dataType.getBeginIndex(), token2.getEndIndex(), dataType, unsignedInteger);
                    continue;
                }
                if (token1.isLeftBracketTrigraph()) {
                    UnsignedInteger unsignedInteger = parseUnsignedInteger();
                    Token token2 = scan();
                    if (!token2.isRightBracketTrigraph()) {
                        throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ??).");
                    }
                    dataType = new ArrayType(dataType.getBeginIndex(), token2.getEndIndex(), dataType, unsignedInteger);
                    continue;
                }
                reset(markedIndex1);
                dataType = new ArrayType(dataType.getBeginIndex(), token.getEndIndex(), dataType, null);
                continue;
            }
            if (token.isMultiset()) {
                dataType = new MultisetType(dataType.getBeginIndex(), token.getEndIndex(), dataType);
                continue;
            }
            reset(markedIndex);
            break;
        }
        return dataType;
    }

    private PredefinedType parsePredefinedType() {
        PredefinedType predefinedType = tryParsePredefinedType();
        if (predefinedType != null) {
            return predefinedType;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to predefined type.");
    }

    private PredefinedType tryParsePredefinedType() {
        PredefinedType predefinedType = tryParseCharStringType();
        if (predefinedType != null) {
            return predefinedType;
        }
        predefinedType = tryParseVarcharStringType();
        if (predefinedType != null) {
            return predefinedType;
        }
        predefinedType = tryParseClobStringType();
        if (predefinedType != null) {
            return predefinedType;
        }
        predefinedType = tryParseNationalCharStringType();
        if (predefinedType != null) {
            return predefinedType;
        }
        predefinedType = tryParseNationalVarcharStringType();
        if (predefinedType != null) {
            return predefinedType;
        }
        predefinedType = tryParseNationalClobStringType();
        if (predefinedType != null) {
            return predefinedType;
        }
        predefinedType = tryParseBinaryLargeObjectStringType();
        if (predefinedType != null) {
            return predefinedType;
        }
        predefinedType = tryParseNumericType();
        if (predefinedType != null) {
            return predefinedType;
        }
        predefinedType = tryParseDecimalType();
        if (predefinedType != null) {
            return predefinedType;
        }
        predefinedType = tryParseSmallintType();
        if (predefinedType != null) {
            return predefinedType;
        }
        predefinedType = tryParseIntegerType();
        if (predefinedType != null) {
            return predefinedType;
        }
        predefinedType = tryParseBigintType();
        if (predefinedType != null) {
            return predefinedType;
        }
        predefinedType = tryParseFloatType();
        if (predefinedType != null) {
            return predefinedType;
        }
        predefinedType = tryParseRealType();
        if (predefinedType != null) {
            return predefinedType;
        }
        predefinedType = tryParseDoublePrecisionType();
        if (predefinedType != null) {
            return predefinedType;
        }
        predefinedType = tryParseBooleanType();
        if (predefinedType != null) {
            return predefinedType;
        }
        predefinedType = tryParseDatetimeType();
        if (predefinedType != null) {
            return predefinedType;
        }
        predefinedType = tryParseIntervalType();
        return predefinedType;
    }

    private NumericType tryParseNumericType() {
        NumericTypeType numericTypeType;
        int markedIndex = mark();
        Token token = scan();
        int beginIndex = token.getBeginIndex();
        if (token.isNumeric()) {
            numericTypeType = NumericTypeType.NUMERIC;
        } else if (token.isDecimal()) {
            numericTypeType = NumericTypeType.DECIMAL;
        } else if (token.isDec()) {
            numericTypeType = NumericTypeType.DEC;
        } else if (token.isSmallint()) {
            numericTypeType = NumericTypeType.SMALLINT;
        } else if (token.isInteger()) {
            numericTypeType = NumericTypeType.INTEGER;
        } else if (token.isInt()) {
            numericTypeType = NumericTypeType.INT;
        } else if (token.isBigint()) {
            numericTypeType = NumericTypeType.BIGINT;
        } else if (token.isFloat()) {
            numericTypeType = NumericTypeType.FLOAT;
        } else if (token.isReal()) {
            numericTypeType = NumericTypeType.REAL;
        } else if (token.isDouble()) {
            token = scan();
            if (!token.isPrecision()) {
                throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a PRECISION.");
            }
            numericTypeType = NumericTypeType.DOUBLE_PRECISION;
        } else {
            reset(markedIndex);
            return null;
        }
        int endIndex = token.getEndIndex();
        UnsignedInteger precision;
        UnsignedInteger scale;
        if (numericTypeType == NumericTypeType.NUMERIC
                || numericTypeType == NumericTypeType.DECIMAL
                || numericTypeType == NumericTypeType.DEC
                || numericTypeType == NumericTypeType.FLOAT) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (token1.isLeftParen()) {
                precision = parseUnsignedInteger();
                if (numericTypeType != NumericTypeType.FLOAT) {
                    int markedIndex2 = mark();
                    Token token2 = scan();
                    if (token2.isComma()) {
                        scale = parseUnsignedInteger();
                    } else {
                        reset(markedIndex2);
                        scale = null;
                    }
                } else {
                    scale = null;
                }
                Token token3 = scan();
                if (!token3.isRightParen()) {
                    throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
                }
                endIndex = token3.getEndIndex();
            } else {
                reset(markedIndex1);
                precision = null;
                scale = null;
            }
        } else {
            precision = null;
            scale = null;
        }
        return new NumericType(token.getBeginIndex(), endIndex, numericTypeType, precision, scale);
    }

    private DecimalType tryParseDecimalType() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDecimal() && !token.isDec()) {
            reset(markedIndex);
            return null;
        }
        int endIndex = token.getEndIndex();
        UnsignedInteger precision;
        UnsignedInteger scale;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isLeftParen()) {
            precision = parseUnsignedInteger();
            int markedIndex2 = mark();
            Token token2 = scan();
            if (token2.isComma()) {
                scale = parseUnsignedInteger();
            } else {
                reset(markedIndex2);
                scale = null;
            }
            Token token3 = scan();
            if (!token3.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
            }
            endIndex = token3.getEndIndex();
        } else {
            reset(markedIndex1);
            precision = null;
            scale = null;
        }
        return new DecimalType(token.getBeginIndex(), endIndex, precision, scale);
    }

    private SmallintType tryParseSmallintType() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSmallint()) {
            reset(markedIndex);
            return null;
        }
        return new SmallintType(token.getBeginIndex(), token.getEndIndex());
    }

    private IntegerType tryParseIntegerType() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isInteger() && !token.isInt()) {
            reset(markedIndex);
            return null;
        }
        return new IntegerType(token.getBeginIndex(), token.getEndIndex());
    }

    private BigintType tryParseBigintType() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isBigint()) {
            reset(markedIndex);
            return null;
        }
        return new BigintType(token.getBeginIndex(), token.getEndIndex());
    }

    private FloatType tryParseFloatType() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDecimal() && !token.isDec()) {
            reset(markedIndex);
            return null;
        }
        int endIndex = token.getEndIndex();
        UnsignedInteger precision;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isLeftParen()) {
            precision = parseUnsignedInteger();
            Token token3 = scan();
            if (!token3.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
            }
            endIndex = token3.getEndIndex();
        } else {
            reset(markedIndex1);
            precision = null;
        }
        return new FloatType(token.getBeginIndex(), endIndex, precision);
    }

    private RealType tryParseRealType() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isReal()) {
            reset(markedIndex);
            return null;
        }
        return new RealType(token.getBeginIndex(), token.getEndIndex());
    }

    private DoublePrecisionType tryParseDoublePrecisionType() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDouble()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isPrecision()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a PRECISION.");
        }
        return new DoublePrecisionType(token.getBeginIndex(), token1.getEndIndex());
    }

    private BooleanType tryParseBooleanType() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDouble()) {
            reset(markedIndex);
            return null;
        }
        return new BooleanType(token.getBeginIndex(), token.getEndIndex());
    }

    private DatetimeType tryParseDatetimeType() {
        boolean date;
        boolean time;
        boolean timestamp;
        UnsignedInteger precision;
        boolean withTimeZone;
        boolean withoutTimeZone;
        int endIndex;
        int markedIndex = mark();
        Token token = scan();
        if (token.isDate()) {
            date = true;
            time = false;
            timestamp = false;
            precision = null;
            withTimeZone = false;
            withoutTimeZone = false;
            endIndex = token.getEndIndex();
        } else if (token.isTime() || token.isTimestamp()) {
            date = false;
            if (token.isTime()) {
                time = true;
                timestamp = false;
            } else {
                time = false;
                timestamp = true;
            }
            int markedIndex1 = mark();
            Token token1 = scan();
            if (token1.isLeftParen()) {
                precision = parseUnsignedInteger();
            } else {
                precision = null;
            }
            Token token2 = scan();
            if (!token2.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
            }
            endIndex = token2.getEndIndex();
            int markedIndex3 = mark();
            Token token3 = scan();
            if (token3.isWith() || token3.isWithout()) {
                if (token3.isWith()) {
                    withTimeZone = true;
                    withoutTimeZone = false;
                } else {
                    withTimeZone = false;
                    withoutTimeZone = true;
                }
                Token token4 = scan();
                if (!token4.isTime()) {
                    throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a TIME.");
                }
                Token token5 = scan();
                if (!token5.isRegularIdentifier() || !"ZONE".equalsIgnoreCase(token5.getContent())) {
                    throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing a ZONE.");
                }
                endIndex = token5.getEndIndex();
            } else {
                reset(markedIndex3);
                withTimeZone = false;
                withoutTimeZone = false;
            }
        } else {
            reset(markedIndex);
            return null;
        }
        return new DatetimeType(
                token.getBeginIndex(),
                endIndex,
                date,
                time,
                timestamp,
                precision,
                withTimeZone,
                withoutTimeZone);
    }

    private IntervalType tryParseIntervalType() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isInterval()) {
            reset(markedIndex);
            return null;
        }
        IntervalQualifier intervalQualifier = parseIntervalQualifier();
        return new IntervalType(token.getBeginIndex(), intervalQualifier);
    }

    private CharStringType tryParseCharStringType() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCharacter() && !token.isChar() && !token.isVarchar()) {
            reset(markedIndex);
            return null;
        }
        int endIndex = token.getEndIndex();
        UnsignedInteger length;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            Token token2 = scan();
            if (token2.isVarying()) {
                reset(markedIndex);
                return null;
            }
            reset(markedIndex1);
            length = null;
        } else {
            length = parseUnsignedInteger();
            Token token2 = scan();
            if (!token2.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
            }
            endIndex = token2.getEndIndex();
        }
        CharacterSetName characterSetSpecification;
        int markedIndex2 = mark();
        Token token2 = scan();
        if (token2.isCharacter()) {
            Token token3 = scan();
            if (!token3.isSet()) {
                throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a SET.");
            }
            characterSetSpecification = parseCharacterSetName();
            endIndex = characterSetSpecification.getEndIndex();
        } else {
            reset(markedIndex2);
            characterSetSpecification = null;
        }
        CollateClause collateClause = tryParseCollateClause();
        if (collateClause != null) {
            endIndex = collateClause.getEndIndex();
        }
        return new CharStringType(token.getBeginIndex(), endIndex, length, characterSetSpecification, collateClause);
    }

    private VarcharStringType tryParseVarcharStringType() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isCharacter() || token.isChar() || token.isVarchar()) {
            if (!token.isVarchar()) {
                Token token1 = scan();
                if (!token1.isVarying()) {
                    reset(markedIndex);
                    return null;
                }
            }
        } else {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        UnsignedInteger length = parseUnsignedInteger();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        int endIndex = token2.getEndIndex();
        CharacterSetName characterSetSpecification;
        int markedIndex3 = mark();
        Token token3 = scan();
        if (token3.isCharacter()) {
            Token token4 = scan();
            if (!token4.isSet()) {
                throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a SET.");
            }
            characterSetSpecification = parseCharacterSetName();
            endIndex = characterSetSpecification.getEndIndex();
        } else {
            reset(markedIndex3);
            characterSetSpecification = null;
        }
        CollateClause collateClause = tryParseCollateClause();
        if (collateClause != null) {
            endIndex = collateClause.getEndIndex();
        }
        return new VarcharStringType(token.getBeginIndex(), endIndex, length, characterSetSpecification, collateClause);
    }

    private ClobStringType tryParseClobStringType() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isCharacter() || token.isChar() || token.isClob()) {
            if (!token.isClob()) {
                Token token1 = scan();
                if (!token1.isLarge()) {
                    reset(markedIndex);
                    return null;
                }
                Token token2 = scan();
                if (!token2.isRegularIdentifier() && !"OBJECT".equalsIgnoreCase(token2.getContent())) {
                    throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a OBJECT.");
                }
            }
        } else {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        LargeObjectLength length = parseLargeObjectLength();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        int endIndex = token2.getEndIndex();
        CharacterSetName characterSetSpecification;
        int markedIndex3 = mark();
        Token token3 = scan();
        if (token3.isCharacter()) {
            Token token4 = scan();
            if (!token4.isSet()) {
                throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a SET.");
            }
            characterSetSpecification = parseCharacterSetName();
            endIndex = characterSetSpecification.getEndIndex();
        } else {
            reset(markedIndex3);
            characterSetSpecification = null;
        }
        CollateClause collateClause = tryParseCollateClause();
        if (collateClause != null) {
            endIndex = collateClause.getEndIndex();
        }
        return new ClobStringType(token.getBeginIndex(), endIndex, length, characterSetSpecification, collateClause);
    }

    private NationalCharStringType tryParseNationalCharStringType() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isNational() && !token.isNchar()) {
            reset(markedIndex);
            return null;
        }
        if (token.isNational()) {
            Token token0 = scan();
            if (!token0.isCharacter() && !token0.isChar()) {
                reset(markedIndex);
                return null;
            }
        }
        int endIndex = token.getEndIndex();
        UnsignedInteger length;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            Token token2 = scan();
            if (token2.isVarying()) {
                reset(markedIndex);
                return null;
            }
            reset(markedIndex1);
            length = null;
        } else {
            length = parseUnsignedInteger();
            Token token2 = scan();
            if (!token2.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
            }
            endIndex = token2.getEndIndex();
        }
        CollateClause collateClause = tryParseCollateClause();
        if (collateClause != null) {
            endIndex = collateClause.getEndIndex();
        }
        return new NationalCharStringType(token.getBeginIndex(), endIndex, length, collateClause);
    }

    private NationalVarcharStringType tryParseNationalVarcharStringType() {
        int markedIndex0 = mark();
        Token token0 = scan();
        if (!token0.isNational() && !token0.isNchar()) {
            reset(markedIndex0);
            return null;
        }
        if (token0.isNational()) {
            Token tokena = scan();
            if (!tokena.isCharacter() && !tokena.isChar()) {
                reset(markedIndex0);
                return null;
            }
            Token tokenb = scan();
            if (!tokenb.isVarying()) {
                reset(markedIndex0);
                return null;
            }
        } else {
            Token tokena = scan();
            if (!tokena.isVarying()) {
                reset(markedIndex0);
                return null;
            }
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        UnsignedInteger length = parseUnsignedInteger();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        int endIndex = token2.getEndIndex();
        CharacterSetName characterSetSpecification;
        int markedIndex3 = mark();
        Token token3 = scan();
        if (token3.isCharacter()) {
            Token token4 = scan();
            if (!token4.isSet()) {
                throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a SET.");
            }
            characterSetSpecification = parseCharacterSetName();
            endIndex = characterSetSpecification.getEndIndex();
        } else {
            reset(markedIndex3);
            characterSetSpecification = null;
        }
        CollateClause collateClause = tryParseCollateClause();
        if (collateClause != null) {
            endIndex = collateClause.getEndIndex();
        }
        return new NationalVarcharStringType(token0.getBeginIndex(), endIndex, length, characterSetSpecification, collateClause);
    }

    private NationalClobStringType tryParseNationalClobStringType() {
        int markedIndex0 = mark();
        Token token0 = scan();
        if (!token0.isNational() && !token0.isNchar() && !token0.isNclob()) {
            reset(markedIndex0);
            return null;
        }
        if (token0.isNational()) {
            Token token = scan();
            if (token.isCharacter()) {
                Token token1 = scan();
                if (!token1.isLarge()) {
                    reset(markedIndex0);
                    return null;
                }
                Token token2 = scan();
                if (!token2.isRegularIdentifier() && !"OBJECT".equalsIgnoreCase(token2.getContent())) {
                    throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a OBJECT.");
                }
            } else {
                reset(markedIndex0);
                return null;
            }
        } else if (token0.isNchar()) {
            Token token1 = scan();
            if (!token1.isLarge()) {
                reset(markedIndex0);
                return null;
            }
            Token token2 = scan();
            if (!token2.isRegularIdentifier() && !"OBJECT".equalsIgnoreCase(token2.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a OBJECT.");
            }
        } else {
            // nclob
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        LargeObjectLength length = parseLargeObjectLength();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        int endIndex = token2.getEndIndex();
        CollateClause collateClause = tryParseCollateClause();
        if (collateClause != null) {
            endIndex = collateClause.getEndIndex();
        }
        return new NationalClobStringType(token0.getBeginIndex(), endIndex, length, collateClause);
    }

    private BinaryLargeObjectStringType tryParseBinaryLargeObjectStringType() {
        int endIndex;
        int markedIndex = mark();
        Token token = scan();
        if (token.isBinary()) {
            Token token1 = scan();
            if (!token1.isLarge()) {
                reset(markedIndex);
                return null;
            }
            Token token2 = scan();
            if (!token2.isRegularIdentifier() || !"OBJECT".equalsIgnoreCase(token2.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a OBJECT.");
            }
            endIndex = token2.getEndIndex();
        } else if (token.isBlob()) {
            endIndex = token.getEndIndex();
        } else {
            reset(markedIndex);
            return null;
        }
        LargeObjectLength largeObjectLength;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isLeftParen()) {
            largeObjectLength = parseLargeObjectLength();
            Token token2 = scan();
            if (!token2.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
            }
        } else {
            reset(markedIndex1);
            largeObjectLength = null;
        }
        return new BinaryLargeObjectStringType(token.getBeginIndex(), endIndex, largeObjectLength);
    }

    private LargeObjectLength parseLargeObjectLength() {
        UnsignedInteger unsignedInteger = parseUnsignedInteger();
        int endIndex = unsignedInteger.getEndIndex();
        Multiplier multiplier = tryParseMultiplier();
        if (multiplier != null) {
            endIndex = multiplier.getEndIndex();
        }
        CharLengthUnits charLengthUnits = tryParseCharLengthUnits();
        if (charLengthUnits != null) {
            endIndex = charLengthUnits.getEndIndex();
        }
        return new LargeObjectLength(unsignedInteger.getBeginIndex(), endIndex, unsignedInteger, multiplier, charLengthUnits);
    }

    private Multiplier tryParseMultiplier() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier()) {
            reset(markedIndex);
            return null;
        }
        String content = token.getContent();
        if ("K".equalsIgnoreCase(content)) {
            return new Multiplier(token.getBeginIndex(), token.getEndIndex(), true, false, false);
        }
        if ("M".equalsIgnoreCase(content)) {
            return new Multiplier(token.getBeginIndex(), token.getEndIndex(), false, true, false);
        }
        if ("G".equalsIgnoreCase(content)) {
            return new Multiplier(token.getBeginIndex(), token.getEndIndex(), false, false, true);
        }
        reset(markedIndex);
        return null;
    }

    private CharLengthUnits parseCharLengthUnits() {
        CharLengthUnits charLengthUnits = tryParseCharLengthUnits();
        if (charLengthUnits != null) {
            return charLengthUnits;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to char length units.");
    }

    private CharLengthUnits tryParseCharLengthUnits() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier()) {
            reset(markedIndex);
            return null;
        }
        String content = token.getContent();
        if ("CHARACTERS".equalsIgnoreCase(content)) {
            return new CharLengthUnits(token.getBeginIndex(), token.getEndIndex(), true, false, false);
        }
        if ("CODE_UNITS".equalsIgnoreCase(content)) {
            return new CharLengthUnits(token.getBeginIndex(), token.getEndIndex(), false, true, false);
        }
        if ("OCTETS".equalsIgnoreCase(content)) {
            return new CharLengthUnits(token.getBeginIndex(), token.getEndIndex(), false, false, true);
        }
        reset(markedIndex);
        return null;
    }

    private RowType tryParseRowType() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRow()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        List<FieldDefinition> fieldDefinitions = new ArrayList<>();
        while (true) {
            FieldDefinition fieldDefinition = parseFieldDefinition();
            int markedIndex2 = mark();
            Token token2 = scan();
            if (!token2.isComma()) {
                reset(markedIndex2);
                break;
            }
            fieldDefinitions.add(fieldDefinition);
        }
        Token token3 = scan();
        if (!token3.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
        }
        return new RowType(token.getBeginIndex(), token3.getEndIndex(), fieldDefinitions);
    }

    private FieldDefinition parseFieldDefinition() {
        Token token = scan();
        if (!token.isRegularIdentifier()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a 字段名.");
        }
        Identifier fieldName = new Identifier(token);
        DataType dataType = parseDataType();
        ReferenceScopeCheck referenceScopeCheck = tryParseReferenceScopeCheck();
        return new FieldDefinition(fieldName, dataType, referenceScopeCheck);
    }

    private ReferenceType tryParseReferenceType() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRef()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        SchemaQualifiedName referencedType = parseSchemaQualifiedName();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        ScopeClause scopeClause = tryParseScopeClause();
        int endIndex;
        if (scopeClause != null) {
            endIndex = scopeClause.getEndIndex();
        } else {
            endIndex = token2.getEndIndex();
        }
        return new ReferenceType(token.getBeginIndex(), endIndex, referencedType, scopeClause);
    }

    private ReferenceScopeCheck tryParseReferenceScopeCheck() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isReferences()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isAre()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ARE.");
        }
        boolean not;
        Token token2 = scan();
        if (token2.isNot()) {
            not = true;
            token2 = scan();
        } else {
            not = false;
        }
        if (!token2.isRegularIdentifier() && !"CHECKED".equalsIgnoreCase(token2.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a CHECKED.");
        }
        int markedIndex3 = mark();
        Token token3 = scan();
        if (!token3.isOn()) {
            reset(markedIndex3);
            return new ReferenceScopeCheck(
                    token.getBeginIndex(),
                    token2.getEndIndex(),
                    not,
                    null);
        }
        Token token4 = scan();
        if (!token4.isDelete()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a DELETE.");
        }
        ReferentialAction referenceScopeCheckAction = parseReferentialAction();
        return new ReferenceScopeCheck(
                token.getBeginIndex(),
                referenceScopeCheckAction.getEndIndex(),
                not,
                referenceScopeCheckAction);
    }

    private DefaultClause tryParseDefaultClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDefault()) {
            reset(markedIndex);
            return null;
        }
        DefaultOption defaultOption = parseDefaultOption();
        return new DefaultClause(token.getBeginIndex(), defaultOption);
    }

    private DefaultOption parseDefaultOption() {
        DefaultOption defaultOption = tryParseLiteral();
        if (defaultOption != null) {
            return defaultOption;
        }
        defaultOption = tryParseDatetimeValueFunction();
        if (defaultOption != null) {
            return defaultOption;
        }
        defaultOption = tryParseUser();
        if (defaultOption != null) {
            return defaultOption;
        }
        defaultOption = tryParseCurrentUser();
        if (defaultOption != null) {
            return defaultOption;
        }
        defaultOption = tryParseCurrentRole();
        if (defaultOption != null) {
            return defaultOption;
        }
        defaultOption = tryParseSessionUser();
        if (defaultOption != null) {
            return defaultOption;
        }
        defaultOption = tryParseSystemUser();
        if (defaultOption != null) {
            return defaultOption;
        }
        defaultOption = tryParseCurrentPath();
        if (defaultOption != null) {
            return defaultOption;
        }
        defaultOption = tryParseImplicitlyTypedValueSpecification();
        if (defaultOption != null) {
            return defaultOption;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to default option.");
    }

    private Literal tryParseLiteral() {
        Literal literal = tryParseSignedNumericLiteral();
        if (literal != null) {
            return literal;
        }
        return tryParseGeneralLiteral();
    }

    private GeneralLiteral tryParseGeneralLiteral() {
        GeneralLiteral generalLiteral = tryParseCharacterStringLiteral();
        if (generalLiteral != null) {
            return generalLiteral;
        }
        generalLiteral = tryParseNationalCharacterStringLiteral();
        if (generalLiteral != null) {
            return generalLiteral;
        }
        generalLiteral = tryParseUnicodeCharacterStringLiteral();
        if (generalLiteral != null) {
            return generalLiteral;
        }
        generalLiteral = tryParseBinaryStringLiteral();
        if (generalLiteral != null) {
            return generalLiteral;
        }
        generalLiteral = tryParseDatetimeLiteral();
        if (generalLiteral != null) {
            return generalLiteral;
        }
        generalLiteral = tryParseIntervalLiteral();
        if (generalLiteral != null) {
            return generalLiteral;
        }
        return tryParseBooleanLiteral();
    }

    private CharacterStringLiteral tryParseCharacterStringLiteral() {
        boolean introducer;
        CharacterSetName characterSetSpecification;
        int markedIndex = mark();
        Token token = scan();
        if (token.isUnderscore()) {
            introducer = true;
            characterSetSpecification = parseCharacterSetName();
        } else {
            reset(markedIndex);
            introducer = false;
            characterSetSpecification = null;
        }
        Token token1 = scan();
        if (!token1.isStringLiteral()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a string literal.");
        }
        List<StringListElement> stringList = new ArrayList<>();
        stringList.add(new StringListElement(token1.getBeginIndex(), token1.getEndIndex(), token1.getContent()));
        int endIndex = token1.getEndIndex();
        while (true) {
            int markedIndex2 = mark();
            Token token2 = scan();
            if (!token2.isStringLiteral()) {
                reset(markedIndex2);
                break;
            }
            stringList.add(new StringListElement(token2.getBeginIndex(), token2.getEndIndex(), token2.getContent()));
            endIndex = token2.getEndIndex();
        }
        return new CharacterStringLiteral(token.getBeginIndex(), endIndex, introducer, characterSetSpecification, new StringList(stringList));
    }

    private CharacterSetName parseCharacterSetName() {
        CharacterSetName characterSetName = tryParseCharacterSetName();
        if (characterSetName != null) {
            return characterSetName;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to character set name.");
    }

    private CharacterSetName tryParseCharacterSetName() {
        Token token1 = scan();
        if (!token1.isRegularIdentifier() && !token1.isUnicodeDelimiterBody() && !token1.isModule()) {
            return null;
        }
        int markedIndex2 = mark();
        Token token2 = scan();
        if (!token2.isPeriod()) {
            reset(markedIndex2);
            return new CharacterSetName(null, new Identifier(token1));
        }
        Token token3 = scan();
        if (!token3.isRegularIdentifier() && !token3.isUnicodeDelimiterBody()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing an identifier.");
        }
        int markedIndex4 = mark();
        Token token4 = scan();
        if (!token4.isPeriod()) {
            reset(markedIndex4);
            return new CharacterSetName(new SchemaName(null, new Identifier(token1)), new Identifier(token3));
        }
        Token token5 = scan();
        if (!token5.isRegularIdentifier() && !token5.isUnicodeDelimiterBody()) {
            throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing an identifier.");
        }
        return new CharacterSetName(new SchemaName(new CatalogName(new Identifier(token1)), new Identifier(token3)), new Identifier(token5));
    }

    private NationalCharacterStringLiteral tryParseNationalCharacterStringLiteral() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || "N".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isStringLiteral()) {
            reset(markedIndex);
            return null;
        }
        List<String> contentList = new ArrayList<>();
        contentList.add(token1.getContent());
        int endIndex = token1.getEndIndex();
        while (true) {
            int markedIndex2 = mark();
            Token token2 = scan();
            if (!token2.isStringLiteral()) {
                reset(markedIndex2);
                break;
            }
            contentList.add(token2.getContent());
            endIndex = token2.getEndIndex();
        }
        return new NationalCharacterStringLiteral(token.getBeginIndex(), endIndex, contentList);
    }

    private UnicodeCharacterStringLiteral tryParseUnicodeCharacterStringLiteral() {
        boolean introducer;
        CharacterSetName characterSetSpecification;
        int markedIndex = mark();
        Token token = scan();
        if (token.isUnderscore()) {
            introducer = true;
            characterSetSpecification = parseCharacterSetName();
        } else {
            reset(markedIndex);
            introducer = false;
            characterSetSpecification = null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"U".equalsIgnoreCase(token1.getContent())) {
            if (introducer == true) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a U.");
            }
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isAmpersand()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a &.");
        }
        Token token3 = scan();
        if (!token3.isStringLiteral()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a 字符串.");
        }
        int endIndex = token3.getEndIndex();
        List<String> contentList = new ArrayList<>();
        contentList.add(token3.getContent());
        while (true) {
            int markedIndex4 = mark();
            Token token4 = scan();
            if (!token4.isStringLiteral()) {
                reset(markedIndex4);
                break;
            }
            contentList.add(token4.getContent());
            endIndex = token4.getEndIndex();
        }
        String escapeCharacter;
        int markedIndex5 = mark();
        Token token5 = scan();
        if (token5.isEscape()) {
            Token token6 = scan();
            if (!token6.isStringLiteral()) {
                throw new YuliaException(getSql(), getLineNumber(), token6.getBeginIndex(), "Missing a escape字符.");
            }
            escapeCharacter = token6.getContent();
            endIndex = token6.getEndIndex();
        } else {
            reset(markedIndex5);
            escapeCharacter = null;
        }
        return new UnicodeCharacterStringLiteral(
                token.getBeginIndex(),
                endIndex,
                introducer,
                characterSetSpecification,
                contentList,
                escapeCharacter
        );
    }

    private BinaryStringLiteral tryParseBinaryStringLiteral() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || "X".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isStringLiteral()) {
            reset(markedIndex);
            return null;
        }
        List<String> contentList = new ArrayList<>();
        contentList.add(token1.getContent());
        int endIndex = token1.getEndIndex();
        while (true) {
            int markedIndex2 = mark();
            Token token2 = scan();
            if (!token2.isStringLiteral()) {
                reset(markedIndex2);
                break;
            }
            contentList.add(token2.getContent());
            endIndex = token2.getEndIndex();
        }
        String escapeCharacter;
        int markedIndex3 = mark();
        Token token3 = scan();
        if (token3.isEscape()) {
            Token token4 = scan();
            if (!token4.isStringLiteral()) {
                throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a escape字符.");
            }
            escapeCharacter = token4.getContent();
            endIndex = token4.getEndIndex();
        } else {
            reset(markedIndex3);
            escapeCharacter = null;
        }
        return new BinaryStringLiteral(token.getBeginIndex(), endIndex, contentList, escapeCharacter);
    }

    private DatetimeLiteral tryParseDatetimeLiteral() {
        DateLiteral dateLiteral = tryParseDateLiteral();
        if (dateLiteral != null) {
            return dateLiteral;
        }
        TimeLiteral timeLiteral = tryParseTimeLiteral();
        if (timeLiteral != null) {
            return timeLiteral;
        }
        TimestampLiteral timestampLiteral = tryParseTimestampLiteral();
        if (timestampLiteral != null) {
            return timestampLiteral;
        }
        return null;
    }

    private DateLiteral tryParseDateLiteral() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDate()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isStringLiteral()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a date string.");
        }
        return new DateLiteral(token.getBeginIndex(), token.getEndIndex(), token1.getContent());
    }

    private TimeLiteral tryParseTimeLiteral() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isTime()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isStringLiteral()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a time string.");
        }
        return new TimeLiteral(token.getBeginIndex(), token.getEndIndex(), token1.getContent());
    }

    private TimestampLiteral tryParseTimestampLiteral() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isTimestamp()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isStringLiteral()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a timestamp string.");
        }
        return new TimestampLiteral(token.getBeginIndex(), token.getEndIndex(), token1.getContent());
    }

    // INTERVAL [ <sign> ] <interval string> <interval qualifier>
    private IntervalLiteral tryParseIntervalLiteral() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isInterval()) {
            reset(markedIndex);
            return null;
        }
        Sign sign = tryParseSign();
        Token token1 = scan();
        if (!token1.isStringLiteral()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a 间隔字符串.");
        }
        IntervalQualifier intervalQualifier = parseIntervalQualifier();
        return new IntervalLiteral(token.getBeginIndex(), intervalQualifier.getEndIndex(), sign, token1.getContent(), intervalQualifier);
    }

    private NonSecondPrimaryDatetimeField tryParseNonSecondPrimaryDatetimeField() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isYear()) {
            return new NonSecondPrimaryDatetimeField(
                    token.getBeginIndex(),
                    token.getEndIndex(),
                    true, false, false, false, false);
        }
        if (token.isMonth()) {
            return new NonSecondPrimaryDatetimeField(
                    token.getBeginIndex(),
                    token.getEndIndex(),
                    false, true, false, false, false);
        }
        if (token.isDay()) {
            return new NonSecondPrimaryDatetimeField(
                    token.getBeginIndex(),
                    token.getEndIndex(),
                    false, false, true, false, false);
        }
        if (token.isHour()) {
            return new NonSecondPrimaryDatetimeField(
                    token.getBeginIndex(),
                    token.getEndIndex(),
                    false, false, false, true, false);
        }
        if (token.isMinute()) {
            return new NonSecondPrimaryDatetimeField(
                    token.getBeginIndex(),
                    token.getEndIndex(),
                    false, false, false, false, true);
        }
        reset(markedIndex);
        return null;
    }

    private PrimaryDatetimeField parsePrimaryDatetimeField() {
        PrimaryDatetimeField primaryDatetimeField = tryParsePrimaryDatetimeField();
        if (primaryDatetimeField != null) {
            return primaryDatetimeField;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resovle to primary datetime field.");
    }

    private PrimaryDatetimeField tryParsePrimaryDatetimeField() {
        NonSecondPrimaryDatetimeField nonSecondPrimaryDatetimeField
                = tryParseNonSecondPrimaryDatetimeField();
        if (nonSecondPrimaryDatetimeField != null) {
            return new PrimaryDatetimeField(nonSecondPrimaryDatetimeField);
        }
        int matkedIndex = mark();
        Token token = scan();
        if (token.isSecond()) {
            return new PrimaryDatetimeField(token.getBeginIndex(), token.getEndIndex());
        }
        reset(matkedIndex);
        return null;
    }

    private EndField tryParseEndField() {
        NonSecondPrimaryDatetimeField nonSecondPrimaryDatetimeField = tryParseNonSecondPrimaryDatetimeField();
        if (nonSecondPrimaryDatetimeField != null) {
            return new EndField(nonSecondPrimaryDatetimeField);
        }
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSecond()) {
            reset(markedIndex);
            return null;
        }
        int markedIndex1 = mark();
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            reset(markedIndex1);
            return new EndField(token.getBeginIndex(), token.getEndIndex());
        }
        UnsignedInteger intervalFractionalSecondsPrecision = parseUnsignedInteger();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new EndField(token.getBeginIndex(), token.getEndIndex(), intervalFractionalSecondsPrecision);
    }

    //   <non-second primary datetime field> [ <left paren> <interval leading field precision> <right paren> ]
    // | SECOND [ <left paren> <interval leading field precision> [ <comma> <interval fractional seconds precision> ] <right paren> ]
    private SingleDatetimeField tryParseSingleDatetimeField() {
        NonSecondPrimaryDatetimeField nonSecondPrimaryDatetimeField = tryParseNonSecondPrimaryDatetimeField();
        if (nonSecondPrimaryDatetimeField != null) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isLeftParen()) {
                reset(markedIndex);
                return new SingleDatetimeField(nonSecondPrimaryDatetimeField);
            }
            UnsignedInteger intervalLeadingFieldPrecision = parseUnsignedInteger();
            Token token1 = scan();
            if (!token1.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
            }
            return new SingleDatetimeField(token1.getEndIndex(), nonSecondPrimaryDatetimeField, intervalLeadingFieldPrecision);
        }
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSecond()) {
            reset(markedIndex);
            return null;
        }
        int markedIndex1 = mark();
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            reset(markedIndex1);
            return new SingleDatetimeField(token.getBeginIndex(), token.getBeginIndex());
        }
        UnsignedInteger intervalLeadingFieldPrecision = parseUnsignedInteger();
        UnsignedInteger intervalFractionalSecondsPrecision;
        Token token2 = scan();
        if (token2.isComma()) {
            intervalFractionalSecondsPrecision = parseUnsignedInteger();
            token2 = scan();
        } else {
            intervalFractionalSecondsPrecision = null;
        }
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new SingleDatetimeField(token.getBeginIndex(), token2.getEndIndex(), intervalLeadingFieldPrecision, intervalFractionalSecondsPrecision);
    }

    private BooleanLiteral tryParseBooleanLiteral() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isTrue()) {
            return new BooleanLiteral(token.getBeginIndex(), token.getEndIndex(), true, false, false);
        }
        if (token.isFalse()) {
            return new BooleanLiteral(token.getBeginIndex(), token.getEndIndex(), false, true, false);
        }
        if (token.isTrue()) {
            return new BooleanLiteral(token.getBeginIndex(), token.getEndIndex(), false, false, true);
        }
        reset(markedIndex);
        return null;
    }

    private DatetimeValueFunction tryParseDatetimeValueFunction() {
        CurrentDateValueFunction currentDateValueFunction
                = tryParseCurrentDateValueFunction();
        if (currentDateValueFunction != null) {
            return currentDateValueFunction;
        }
        CurrentTimeValueFunction currentTimeValueFunction
                = tryParseCurrentTimeValueFunction();
        if (currentTimeValueFunction != null) {
            return currentTimeValueFunction;
        }
        CurrentTimestampValueFunction currentTimestampValueFunction
                = tryParseCurrentTimestampValueFunction();
        if (currentTimestampValueFunction != null) {
            return currentTimestampValueFunction;
        }
        CurrentLocalTimeValueFunction currentLocalTimeValueFunction
                = tryParseCurrentLocalTimeValueFunction();
        if (currentLocalTimeValueFunction != null) {
            return currentLocalTimeValueFunction;
        }
        CurrentLocalTimestampValueFunction currentLocalTimestampValueFunction
                = tryParseCurrentLocalTimestampValueFunction();
        if (currentLocalTimestampValueFunction != null) {
            return currentLocalTimestampValueFunction;
        }
        return null;
    }

    private CurrentDateValueFunction tryParseCurrentDateValueFunction() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCurrentDate()) {
            reset(markedIndex);
            return null;
        }
        return new CurrentDateValueFunction(token.getBeginIndex(), token.getEndIndex());
    }

    private CurrentTimeValueFunction tryParseCurrentTimeValueFunction() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCurrentTime()) {
            reset(markedIndex);
            return null;
        }
        UnsignedInteger timePrecision;
        int endIndex;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isLeftParen()) {
            timePrecision = parseUnsignedInteger();
            Token token2 = scan();
            if (!token2.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
            }
            endIndex = token2.getEndIndex();
        } else {
            reset(markedIndex1);
            timePrecision = null;
            endIndex = token.getEndIndex();
        }
        return new CurrentTimeValueFunction(token.getBeginIndex(), endIndex, timePrecision);
    }

    private CurrentTimestampValueFunction tryParseCurrentTimestampValueFunction() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCurrentTimestamp()) {
            reset(markedIndex);
            return null;
        }
        UnsignedInteger timestampPrecision;
        int endIndex;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isLeftParen()) {
            timestampPrecision = parseUnsignedInteger();
            Token token2 = scan();
            if (!token2.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
            }
            endIndex = token2.getEndIndex();
        } else {
            reset(markedIndex1);
            timestampPrecision = null;
            endIndex = token.getEndIndex();
        }
        return new CurrentTimestampValueFunction(token.getBeginIndex(), endIndex, timestampPrecision);
    }

    private CurrentLocalTimeValueFunction tryParseCurrentLocalTimeValueFunction() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLocaltime()) {
            reset(markedIndex);
            return null;
        }
        UnsignedInteger timePrecision;
        int endIndex;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isLeftParen()) {
            timePrecision = parseUnsignedInteger();
            Token token2 = scan();
            if (!token2.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
            }
            endIndex = token2.getEndIndex();
        } else {
            reset(markedIndex1);
            timePrecision = null;
            endIndex = token.getEndIndex();
        }
        return new CurrentLocalTimeValueFunction(token.getBeginIndex(), endIndex, timePrecision);
    }

    private CurrentLocalTimestampValueFunction tryParseCurrentLocalTimestampValueFunction() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLocaltimestamp()) {
            reset(markedIndex);
            return null;
        }
        UnsignedInteger timestampPrecision;
        int endIndex;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isLeftParen()) {
            timestampPrecision = parseUnsignedInteger();
            Token token2 = scan();
            if (!token2.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
            }
            endIndex = token2.getEndIndex();
        } else {
            reset(markedIndex1);
            timestampPrecision = null;
            endIndex = token.getEndIndex();
        }
        return new CurrentLocalTimestampValueFunction(token.getBeginIndex(), endIndex, timestampPrecision);
    }

    private UnsignedInteger parseUnsignedInteger() {
        UnsignedInteger unsignedInteger = tryParseUnsignedInteger();
        if (unsignedInteger != null) {
            return unsignedInteger;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a 无符号整数.");
    }

    private UnsignedInteger tryParseUnsignedInteger() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isUnsignedIntegerLiteral()) {
            reset(markedIndex);
            return null;
        }
        return new UnsignedInteger(token.getBeginIndex(), token.getEndIndex(), token.getContent());
    }

    private SignedInteger parseSignedInteger() {
        SignedInteger signedInteger = tryParseSignedInteger();
        if (signedInteger != null) {
            return signedInteger;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a 有符号整数.");
    }

    private SignedInteger tryParseSignedInteger() {
        Sign sign = tryParseSign();
        UnsignedInteger unsignedInteger;
        if (sign == null) {
            unsignedInteger = tryParseUnsignedInteger();
            if (unsignedInteger == null) {
                return null;
            }
        } else {
            unsignedInteger = parseUnsignedInteger();
        }
        return new SignedInteger(sign, unsignedInteger);
    }

    private User tryParseUser() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isUser()) {
            reset(markedIndex);
            return null;
        }
        return new User(token.getBeginIndex(), token.getEndIndex());
    }

    private CurrentUser tryParseCurrentUser() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCurrentUser()) {
            reset(markedIndex);
            return null;
        }
        return new CurrentUser(token.getBeginIndex(), token.getEndIndex());
    }

    private CurrentRole tryParseCurrentRole() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCurrentRole()) {
            reset(markedIndex);
            return null;
        }
        return new CurrentRole(token.getBeginIndex(), token.getEndIndex());
    }

    private SessionUser tryParseSessionUser() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSessionUser()) {
            reset(markedIndex);
            return null;
        }
        return new SessionUser(token.getBeginIndex(), token.getEndIndex());
    }

    private SystemUser tryParseSystemUser() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSystemUser()) {
            reset(markedIndex);
            return null;
        }
        return new SystemUser(token.getBeginIndex(), token.getEndIndex());
    }

    private CurrentPath tryParseCurrentPath() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCurrentPath()) {
            reset(markedIndex);
            return null;
        }
        return new CurrentPath(token.getBeginIndex(), token.getEndIndex());
    }

    private ImplicitlyTypedValueSpecification tryParseImplicitlyTypedValueSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isNull()) {
            return new ImplicitlyTypedValueSpecification(
                    token.getBeginIndex(), token.getEndIndex(), true, false, false
            );
        }
        if (token.isArray() || token.isMultiset()) {
            boolean array;
            boolean multiset;
            if (token.isArray()) {
                array = true;
                multiset = false;
            } else {
                array = false;
                multiset = true;
            }
            int endIndex;
            Token token1 = scan();
            if (token.isLeftBracket()) {
                Token token2 = scan();
                if (!token2.isRightBracket()) {
                    throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ].");
                }
                endIndex = token2.getEndIndex();
            } else if (token1.isLeftBracketTrigraph()) {
                Token token2 = scan();
                if (!token2.isRightBracketTrigraph()) {
                    throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ??).");
                }
                endIndex = token2.getEndIndex();
            } else {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a [或??(.");
            }
            return new ImplicitlyTypedValueSpecification(
                    token.getBeginIndex(), endIndex, false, array, multiset
            );
        }
        reset(markedIndex);
        return null;
    }

    private DefaultClause parseDefaultClause() {
        DefaultClause defaultClause = tryParseDefaultClause();
        if (defaultClause == null) {
            Token token = scan();
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a DEFAULT.");
        }
        return defaultClause;
    }

    private IdentityColumnSpecification tryParseIdentityColumnSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"GENERATED".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        boolean always;
        boolean byDefault;
        Token token1 = scan();
        if (token1.isRegularIdentifier() && "ALWAYS".equalsIgnoreCase(token1.getContent())) {
            always = true;
            byDefault = false;
        } else if (token1.isBy()) {
            Token token2 = scan();
            if (!token2.isDefault()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a DEFAULT.");
            }
            always = false;
            byDefault = true;
        } else {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ALWAYS或BY DEFAULT.");
        }
        Token token2 = scan();
        if (!token2.isAs()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a AS.");
        }
        Token token3 = scan();
        if (!token3.isIdentity()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a IDENTITY.");
        }
        int markedIndex4 = mark();
        Token token4 = scan();
        if (!token4.isLeftParen()) {
            reset(markedIndex4);
            return new IdentityColumnSpecification(token.getBeginIndex(), token3.getEndIndex(), always, byDefault, null);
        }
        CommonSequenceGeneratorOptions commonSequenceGeneratorOptions = parseCommonSequenceGeneratorOptions();
        Token token5 = scan();
        if (!token5.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing a ).");
        }
        return new IdentityColumnSpecification(
                token.getBeginIndex(),
                token3.getEndIndex(),
                always,
                byDefault,
                commonSequenceGeneratorOptions);
    }

    private CommonSequenceGeneratorOptions parseCommonSequenceGeneratorOptions() {
        CommonSequenceGeneratorOptions commonSequenceGeneratorOptions = tryParseCommonSequenceGeneratorOptions();
        if (commonSequenceGeneratorOptions != null) {
            return commonSequenceGeneratorOptions;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to common sequence generator options.");
    }

    private CommonSequenceGeneratorOption tryParseCommonSequenceGeneratorOption() {
        CommonSequenceGeneratorOption commonSequenceGeneratorOption = tryParseSequenceGeneratorStartWithOption();
        if (commonSequenceGeneratorOption != null) {
            return commonSequenceGeneratorOption;
        }
        commonSequenceGeneratorOption = tryParseBasicSequenceGeneratorOption();
        return commonSequenceGeneratorOption;
    }

    private SequenceGeneratorStartWithOption tryParseSequenceGeneratorStartWithOption() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isStart()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isWith()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a WITH.");
        }
        SignedNumericLiteral sequenceGeneratorStartValue = parseSignedNumericLiteral();
        return new SequenceGeneratorStartWithOption(token.getBeginIndex(), sequenceGeneratorStartValue);
    }

    private BasicSequenceGeneratorOption parseBasicSequenceGeneratorOption() {
        BasicSequenceGeneratorOption basicSequenceGeneratorOption = tryParseBasicSequenceGeneratorOption();
        if (basicSequenceGeneratorOption != null) {
            return basicSequenceGeneratorOption;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to basic sequence generator option.");
    }

    private BasicSequenceGeneratorOption tryParseBasicSequenceGeneratorOption() {
        BasicSequenceGeneratorOption basicSequenceGeneratorOption = tryParseSequenceGeneratorIncrementByOption();
        if (basicSequenceGeneratorOption != null) {
            return basicSequenceGeneratorOption;
        }
        basicSequenceGeneratorOption = tryParseSequenceGeneratorMaxvalueOption();
        if (basicSequenceGeneratorOption != null) {
            return basicSequenceGeneratorOption;
        }
        basicSequenceGeneratorOption = tryParseSequenceGeneratorMinvalueOption();
        if (basicSequenceGeneratorOption != null) {
            return basicSequenceGeneratorOption;
        }
        basicSequenceGeneratorOption = tryParseSequenceGeneratorCycleOption();
        return basicSequenceGeneratorOption;
    }

    // INCREMENT BY <sequence generator increment>
    private SequenceGeneratorIncrementByOption tryParseSequenceGeneratorIncrementByOption() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"INCREMENT".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isBy()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a BY.");
        }
        SignedNumericLiteral sequenceGeneratorIncrement = parseSignedNumericLiteral();
        return new SequenceGeneratorIncrementByOption(token.getBeginIndex(), sequenceGeneratorIncrement);
    }

    private SequenceGeneratorMaxvalueOption tryParseSequenceGeneratorMaxvalueOption() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "MAXVALUE".equalsIgnoreCase(token.getContent())) {
            SignedNumericLiteral sequenceGeneratorMaxValue = parseSignedNumericLiteral();
            return new SequenceGeneratorMaxvalueOption(token.getBeginIndex(), sequenceGeneratorMaxValue.getEndIndex(), false, sequenceGeneratorMaxValue);
        }
        if (token.isNo()) {
            Token token1 = scan();
            if (!token1.isRegularIdentifier() && !"MAXVALUE".equalsIgnoreCase(token1.getContent())) {
                reset(markedIndex);
                return null;
            }
            return new SequenceGeneratorMaxvalueOption(token.getBeginIndex(), token1.getEndIndex(), true, null);
        }
        reset(markedIndex);
        return null;
    }

    private SignedNumericLiteral parseSignedNumericLiteral() {
        SignedNumericLiteral signedNumericLiteral = tryParseSignedNumericLiteral();
        if (signedNumericLiteral != null) {
            return signedNumericLiteral;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a 有符号数字字面量.");
    }

    private SignedNumericLiteral tryParseSignedNumericLiteral() {
        Sign sign = tryParseSign();
        UnsignedNumericLiteral unsignedNumericLiteral;
        if (sign == null) {
            unsignedNumericLiteral = tryParseUnsignedNumericLiteral();
            if (unsignedNumericLiteral == null) {
                return null;
            }
        } else {
            unsignedNumericLiteral = parseUnsignedNumericLiteral();
        }
        return new SignedNumericLiteral(sign, unsignedNumericLiteral);
    }

    private UnsignedNumericLiteral parseUnsignedNumericLiteral() {
        UnsignedNumericLiteral unsignedNumericLiteral = tryParseUnsignedNumericLiteral();
        if (unsignedNumericLiteral != null) {
            return unsignedNumericLiteral;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a 无符号数字字面量.");
    }

    private UnsignedNumericLiteral tryParseUnsignedNumericLiteral() {
        UnsignedInteger unsignedInteger1 = tryParseUnsignedInteger();
        UnsignedInteger unsignedInteger2;
        int markedIndex = mark();
        Token token = scan();
        if (token.isPeriod()) {
            unsignedInteger2 = parseUnsignedInteger();
        } else {
            reset(markedIndex);
            unsignedInteger2 = null;
        }
        SignedInteger exponent;
        int markedIndex1 = markedIndex;
        Token token1 = scan();
        if (token1.isRegularIdentifier()) {
            String content = token1.getContent();
            if ("e".equalsIgnoreCase(content)) {
                exponent = tryParseSignedInteger();
            } else {
                reset(markedIndex1);
                exponent = null;
            }
        } else {
            reset(markedIndex1);
            exponent = null;
        }
        if (unsignedInteger1 == null && unsignedInteger2 == null && exponent == null) {
            return null;
        }
        int beginIndex;
        if (unsignedInteger1 != null) {
            beginIndex = unsignedInteger1.getBeginIndex();
        } else {
            beginIndex = token.getBeginIndex();
        }
        return new UnsignedNumericLiteral(beginIndex, unsignedInteger1, unsignedInteger2, exponent);
    }

    private Sign tryParseSign() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isPlusSign()) {
            return new PlusSign(token.getBeginIndex(), token.getEndIndex());
        }
        if (token.isMinusSign()) {
            return new MinusSign(token.getBeginIndex(), token.getEndIndex());
        }
        reset(markedIndex);
        return null;
    }

    private SequenceGeneratorMinvalueOption tryParseSequenceGeneratorMinvalueOption() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "MINVALUE".equalsIgnoreCase(token.getContent())) {
            SignedNumericLiteral sequenceGeneratorMinValue = parseSignedNumericLiteral();
            return new SequenceGeneratorMinvalueOption(token.getBeginIndex(), sequenceGeneratorMinValue.getEndIndex(), false, sequenceGeneratorMinValue);
        }
        if (token.isNo()) {
            Token token1 = scan();
            if (!token1.isRegularIdentifier() && !"MINVALUE".equalsIgnoreCase(token1.getContent())) {
                reset(markedIndex);
                return null;
            }
            return new SequenceGeneratorMinvalueOption(token.getBeginIndex(), token1.getEndIndex(), true, null);
        }
        reset(markedIndex);
        return null;
    }

    private SequenceGeneratorCycleOption tryParseSequenceGeneratorCycleOption() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isCycle()) {
            return new SequenceGeneratorCycleOption(token.getBeginIndex(), token.getEndIndex(), false);
        }
        if (!token.isNo()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isCycle()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a CYCLE.");
        }
        return new SequenceGeneratorCycleOption(token.getBeginIndex(), token1.getEndIndex(), true);
    }

    private GenerationClause tryParseGenerationClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"GENERATED".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"ALWAYS".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ALWAYS.");
        }
        Token token2 = scan();
        if (!token2.isAs()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a AS.");
        }
        Token token3 = scan();
        if (!token3.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a (.");
        }
        ValueExpression generationExpression = parseValueExpression();
        Token token4 = scan();
        if (!token4.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a ).");
        }
        return new GenerationClause(token.getBeginIndex(), token4.getEndIndex(), generationExpression);
    }

    private ColumnConstraintDefinition tryParseColumnConstraintDefinition() {
        ConstraintNameDefinition constraintNameDefinition = tryParseConstraintNameDefinition();
        ColumnConstraint columnConstraint = parseColumnConstraint();
        ConstraintCharacteristics constraintCharacteristics = tryParseConstraintCharacteristics();
        int beginIndex;
        if (constraintNameDefinition != null) {
            beginIndex = constraintNameDefinition.getBeginIndex();
        } else {
            beginIndex = columnConstraint.getBeginIndex();
        }
        int endIndex;
        if (constraintCharacteristics != null) {
            endIndex = constraintCharacteristics.getEndIndex();
        } else {
            endIndex = columnConstraint.getEndIndex();
        }
        return new ColumnConstraintDefinition(
                beginIndex,
                endIndex,
                constraintNameDefinition,
                columnConstraint,
                constraintCharacteristics);
    }

    private ColumnConstraint parseColumnConstraint() {
        ColumnConstraint columnConstraint = tryParseNotNull();
        if (columnConstraint != null) {
            return columnConstraint;
        }
        columnConstraint = tryParseUniqueSpecification();
        if (columnConstraint != null) {
            return columnConstraint;
        }
        columnConstraint = tryParseReferencesSpecification();
        if (columnConstraint != null) {
            return columnConstraint;
        }
        columnConstraint = tryParseCheckConstraintDefinition();
        if (columnConstraint != null) {
            return columnConstraint;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to column constraint.");
    }

    private NotNull tryParseNotNull() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isNot()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isNull()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a NULL.");
        }
        return new NotNull(token.getBeginIndex(), token1.getEndIndex());
    }

    private CollateClause tryParseCollateClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCollate()) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName collationName = parseSchemaQualifiedName();
        return new CollateClause(token.getBeginIndex(), collationName);
    }

    private AlterColumnAction parseAlterColumnAction() {
        AlterColumnAction alterColumnAction = tryParseSetColumnDefaultClause();
        if (alterColumnAction != null) {
            return alterColumnAction;
        }
        alterColumnAction = tryParseDropColumnDefaultClause();
        if (alterColumnAction != null) {
            return alterColumnAction;
        }
        alterColumnAction = tryParseAddColumnScopeClause();
        if (alterColumnAction != null) {
            return alterColumnAction;
        }
        alterColumnAction = tryParseDropColumnScopeClause();
        if (alterColumnAction != null) {
            return alterColumnAction;
        }
        alterColumnAction = tryParseAlterIdentityColumnSpecification();
        return alterColumnAction;
    }

    // SET <default clause>
    private SetColumnDefaultClause tryParseSetColumnDefaultClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            reset(markedIndex);
            return null;
        }
        DefaultClause defaultClause = parseDefaultClause();
        return new SetColumnDefaultClause(token.getBeginIndex(), defaultClause);
    }

    // DROP DEFAULT
    private DropColumnDefaultClause tryParseDropColumnDefaultClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isDefault()) {
            reset(markedIndex);
            return null;
        }
        return new DropColumnDefaultClause(token.getBeginIndex(), token1.getEndIndex());
    }

    // ADD <scope clause>
    private AddColumnScopeClause tryParseAddColumnScopeClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAdd()) {
            reset(markedIndex);
            return null;
        }
        ScopeClause scopeClause = parseScopeClause();
        return new AddColumnScopeClause(token.getBeginIndex(), scopeClause);
    }

    // SCOPE <table name>
    private ScopeClause parseScopeClause() {
        ScopeClause scopeClause = tryParseScopeClause();
        if (scopeClause != null) {
            return scopeClause;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to scope clause.");
    }

    // SCOPE <table name>
    private ScopeClause tryParseScopeClause() {
        Token token = scan();
        if (!token.isRegularIdentifier() || !"SCOPE".equalsIgnoreCase(token.getContent())) {
            return null;
        }
        LocalOrSchemaQualifiedName tableName = parseLocalOrSchemaQualifiedName();
        return new ScopeClause(token.getBeginIndex(), tableName);
    }

    // DROP SCOPE <drop behavior>
    private DropColumnScopeClause tryParseDropColumnScopeClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"SCOPE".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        DropBehavior dropBehavior = parseDropBehavior();
        return new DropColumnScopeClause(token.getBeginIndex(), dropBehavior);
    }

    // <alter identity column option> ...
    private AlterIdentityColumnSpecification tryParseAlterIdentityColumnSpecification() {
        AlterIdentityColumnOption alterIdentityColumnOption = tryParseAlterIdentityColumnOption();
        if (alterIdentityColumnOption == null) {
            return null;
        }
        List<AlterIdentityColumnOption> alterIdentityColumnOptions = new ArrayList<>();
        alterIdentityColumnOptions.add(alterIdentityColumnOption);
        return new AlterIdentityColumnSpecification(alterIdentityColumnOptions);
    }

    private AlterIdentityColumnOption tryParseAlterIdentityColumnOption() {
        AlterIdentityColumnOption alterIdentityColumnOption = tryParseAlterSequenceGeneratorRestartOption();
        if (alterIdentityColumnOption != null) {
            return alterIdentityColumnOption;
        }
        alterIdentityColumnOption = tryParseSetBasicSequenceGeneratorOption();
        return alterIdentityColumnOption;
    }

    // RESTART WITH <sequence generator restart value>
    private AlterSequenceGeneratorRestartOption tryParseAlterSequenceGeneratorRestartOption() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"RESTART".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isWith()) {
            reset(markedIndex);
            return null;
        }
        SignedNumericLiteral sequenceGeneratorRestartValue = parseSignedNumericLiteral();
        return new AlterSequenceGeneratorRestartOption(token.getBeginIndex(), sequenceGeneratorRestartValue);
    }

    // SET <basic sequence generator option>
    private SetBasicSequenceGeneratorOption tryParseSetBasicSequenceGeneratorOption() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            reset(markedIndex);
            return null;
        }
        BasicSequenceGeneratorOption basicSequenceGeneratorOption = parseBasicSequenceGeneratorOption();
        return new SetBasicSequenceGeneratorOption(token.getBeginIndex(), basicSequenceGeneratorOption);
    }

    private TableConstraintDefinition parseTableConstraintDefinition() {
        TableConstraintDefinition tableConstraintDefinition = tryParseTableConstraintDefinition();
        if (tableConstraintDefinition != null) {
            return tableConstraintDefinition;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to table constraint definition.");
    }

    private TableConstraintDefinition tryParseTableConstraintDefinition() {
        int markedIndex = mark();
        Token token = scan();
        reset(markedIndex);
        ConstraintNameDefinition constraintNameDefinition = tryParseConstraintNameDefinition();
        TableConstraint tableConstraint = tryParseUniqueConstraintDefinition();
        if (tableConstraint == null) {
            tableConstraint = tryParseReferentialConstraintDefinition();
        }
        if (tableConstraint == null) {
            tableConstraint = parseCheckConstraintDefinition();
        }
        if (constraintNameDefinition == null && tableConstraint == null) {
            return null;
        }
        ConstraintCharacteristics constraintCharacteristics = tryParseConstraintCharacteristics();
        return new TableConstraintDefinition(
                token.getBeginIndex(),
                token.getEndIndex(),
                constraintNameDefinition,
                tableConstraint,
                constraintCharacteristics);
    }

    private ConstraintNameDefinition tryParseConstraintNameDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isConstraint()) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName constraintName = parseSchemaQualifiedName();
        return new ConstraintNameDefinition(token.getBeginIndex(), constraintName);
    }

    private UniqueConstraintDefinition tryParseUniqueConstraintDefinition() {
        UniqueSpecification uniqueSpecification = tryParseUniqueSpecification();
        if (uniqueSpecification == null) {
            return null;
        }
        Token token = scan();
        if (!token.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a (.");
        }
        IdentifierList uniqueColumnList;
        if (uniqueSpecification.isUnique()) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (token1.isValue()) {
                uniqueColumnList = null;
            } else {
                reset(markedIndex1);
                uniqueColumnList = parseIdentifierList();
            }
        } else {
            uniqueColumnList = parseIdentifierList();
        }
        Token token2 = scan();
        if (!token.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new UniqueConstraintDefinition(
                uniqueSpecification.getBeginIndex(),
                token2.getEndIndex(),
                uniqueSpecification,
                uniqueColumnList);
    }

    private UniqueSpecification tryParseUniqueSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isUnique()) {
            return new UniqueSpecification(token.getBeginIndex(), token.getEndIndex(), true, false);
        }
        if (token.isPrimary()) {
            Token token1 = scan();
            if (!token1.isRegularIdentifier() || "KEY".equalsIgnoreCase(token1.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a KEY.");
            }
            return new UniqueSpecification(token.getBeginIndex(), token1.getEndIndex(), false, true);
        }
        reset(markedIndex);
        return null;
    }

    private IdentifierChain parseIdentifierChain() {
        IdentifierChain identifierChain = tryParseIdentifierChain();
        if (identifierChain != null) {
            return identifierChain;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to identifier chain.");
    }

    private List<SpecificRoutineDesignator> parsePrivilegeMethodList() {
        SpecificRoutineDesignator privilegeMethod = parseSpecificRoutineDesignator();
        List<SpecificRoutineDesignator> privilegeMethodList = new ArrayList<>();
        privilegeMethodList.add(privilegeMethod);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isComma()) {
                reset(markedIndex);
                break;
            }
            SpecificRoutineDesignator privilegeMethod1 = parseSpecificRoutineDesignator();
            privilegeMethodList.add(privilegeMethod1);
        }
        return privilegeMethodList;
    }

    private ReferentialConstraintDefinition tryParseReferentialConstraintDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isForeign()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || "KEY".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a KEY.");
        }
        Token token2 = scan();
        if (!token2.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a (.");
        }
        IdentifierList referencingColumns = parseIdentifierList();
        Token token3 = scan();
        if (!token3.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
        }
        ReferencesSpecification referencesSpecification = parseReferencesSpecification();
        return new ReferentialConstraintDefinition(token.getBeginIndex(), referencingColumns, referencesSpecification);
    }

    private ReferencesSpecification parseReferencesSpecification() {
        ReferencesSpecification referencesSpecification = tryParseReferencesSpecification();
        if (referencesSpecification == null) {
            Token token = scan();
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a REFERENCES.");
        }
        return referencesSpecification;
    }

    private ReferencesSpecification tryParseReferencesSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isReferences()) {
            reset(markedIndex);
            return null;
        }
        ReferencedTableAndColumns referencedTableAndColumns = parseReferencedTableAndColumns();
        MatchType matchType;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isMatch()) {
            matchType = parseMatchType();
        } else {
            reset(markedIndex1);
            matchType = null;
        }
        ReferentialTriggeredAction referentialTriggeredAction = tryParseReferentialTriggeredAction();
        return new ReferencesSpecification(
                token.getBeginIndex(),
                referencedTableAndColumns,
                matchType,
                referentialTriggeredAction);
    }

    public ReferencedTableAndColumns parseReferencedTableAndColumns() {
        int markedIndex = mark();
        Token token = scan();
        reset(markedIndex);
        LocalOrSchemaQualifiedName tableName = parseLocalOrSchemaQualifiedName();
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        IdentifierList referenceColumnList = parseIdentifierList();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new ReferencedTableAndColumns(token.getBeginIndex(), token2.getEndIndex(), tableName, referenceColumnList);
    }

    public MatchType parseMatchType() {
        Token token = scan();
        if (token.isFull()) {
            return new MatchType(token.getBeginIndex(), token.getEndIndex(), true, false, false);
        }
        if (token.isRegularIdentifier() && "PARTIAL".equalsIgnoreCase(token.getContent())) {
            return new MatchType(token.getBeginIndex(), token.getEndIndex(), false, true, false);
        }
        if (token.isRegularIdentifier() && "SIMPLE".equalsIgnoreCase(token.getContent())) {
            return new MatchType(token.getBeginIndex(), token.getEndIndex(), false, false, true);
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a FULL, PARTIAL or SIMPLE.");
    }

    private ReferentialTriggeredAction tryParseReferentialTriggeredAction() {
        DeleteRule deleteRule = tryParseDeleteRule();
        if (deleteRule == null) {
            UpdateRule updateRule = tryParseUpdateRule();
            if (updateRule == null) {
                return null;
            }
            deleteRule = tryParseDeleteRule();
            int endIndex;
            if (deleteRule == null) {
                endIndex = updateRule.getEndIndex();
            } else {
                endIndex = deleteRule.getEndIndex();
            }
            return new ReferentialTriggeredAction(updateRule.getBeginIndex(), endIndex, deleteRule, updateRule);
        }
        UpdateRule updateRule = tryParseUpdateRule();
        int endIndex;
        if (updateRule == null) {
            endIndex = deleteRule.getEndIndex();
        } else {
            endIndex = updateRule.getEndIndex();
        }
        return new ReferentialTriggeredAction(updateRule.getBeginIndex(), endIndex, deleteRule, updateRule);
    }

    private DeleteRule tryParseDeleteRule() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isOn()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isDelete()) {
            reset(markedIndex);
            return null;
        }
        return new DeleteRule(token.getBeginIndex(), parseReferentialAction());
    }

    private UpdateRule tryParseUpdateRule() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isOn()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isUpdate()) {
            reset(markedIndex);
            return null;
        }
        return new UpdateRule(token.getBeginIndex(), parseReferentialAction());
    }

    private ReferentialAction parseReferentialAction() {
        Token token = scan();
        if (token.isRegularIdentifier()) {
            String content = token.getContent();
            if ("CASCADE".equalsIgnoreCase(content)) {
                return new ReferentialAction(token.getBeginIndex(), token.getEndIndex(), true, false, false, false, false);
            }
            if ("RESTRICT".equalsIgnoreCase(content)) {
                return new ReferentialAction(token.getBeginIndex(), token.getEndIndex(), false, false, false, true, false);
            }
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a CASCADE或RESTRICT.");
        } else if (token.isSet()) {
            Token token1 = scan();
            if (token1.isNull()) {
                return new ReferentialAction(token.getBeginIndex(), token.getEndIndex(), false, true, false, false, false);
            }
            if (token1.isDefault()) {
                return new ReferentialAction(token.getBeginIndex(), token.getEndIndex(), false, false, true, false, false);
            }
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a NULL或DEFAULT.");
        } else if (token.isNo()) {
            Token token1 = scan();
            if (token1.isRegularIdentifier() && "ACTION".equalsIgnoreCase(token1.getContent())) {
                return new ReferentialAction(token.getBeginIndex(), token.getEndIndex(), false, false, false, false, true);
            }
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a CASCADE、RESTRICT、SET或NO.");
    }

    private CheckConstraintDefinition parseCheckConstraintDefinition() {
        CheckConstraintDefinition checkConstraintDefinition = tryParseCheckConstraintDefinition();
        if (checkConstraintDefinition == null) {
            Token token = scan();
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a CHECK.");
        }
        return checkConstraintDefinition;
    }

    private CheckConstraintDefinition tryParseCheckConstraintDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCheck()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        BooleanValueExpression searchCondition = parseBooleanValueExpression();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new CheckConstraintDefinition(token.getBeginIndex(), token2.getEndIndex(), searchCondition);
    }

    private ConstraintCharacteristics tryParseConstraintCharacteristics() {
        ConstraintCheckTime constraintCheckTime = null;
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "INITIALLY".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            constraintCheckTime = parseConstraintCheckTime();
            boolean not;
            boolean deferrable;
            int endIndex;
            int markedIndex1 = mark();
            Token token1 = scan();
            if (token1.isNot()) {
                not = true;
                Token token2 = scan();
                if (!token2.isRegularIdentifier() || !"DEFERRABLE".equalsIgnoreCase(token2.getContent())) {
                    throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a DEFERRABLE.");
                }
                deferrable = true;
                endIndex = token2.getEndIndex();
            } else if (token1.isRegularIdentifier() && "DEFERRABLE".equalsIgnoreCase(token1.getContent())) {
                not = false;
                deferrable = true;
                endIndex = token1.getEndIndex();
            } else {
                reset(markedIndex1);
                not = false;
                deferrable = false;
                endIndex = constraintCheckTime.getEndIndex();
            }
            return new ConstraintCharacteristics(token.getBeginIndex(), endIndex, constraintCheckTime, not, deferrable);
        }
        if (token.isNot()) {
            boolean not = true;
            Token token1 = scan();
            if (!token1.isRegularIdentifier() || !"DEFERRABLE".equalsIgnoreCase(token1.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a DEFERRABLE.");
            }
            boolean deferrable = true;
            constraintCheckTime = tryParseConstraintCheckTime();
            int endIndex;
            if (constraintCheckTime != null) {
                endIndex = constraintCheckTime.getEndIndex();
            } else {
                endIndex = token1.getEndIndex();
            }
            return new ConstraintCharacteristics(token.getBeginIndex(), endIndex, constraintCheckTime, not, deferrable);
        }
        if (token.isRegularIdentifier() && "DEFERRABLE".equalsIgnoreCase(token.getContent())) {
            boolean not = false;
            boolean deferrable = true;
            constraintCheckTime = tryParseConstraintCheckTime();
            int endIndex;
            if (constraintCheckTime != null) {
                endIndex = constraintCheckTime.getEndIndex();
            } else {
                endIndex = token.getEndIndex();
            }
            return new ConstraintCharacteristics(token.getBeginIndex(), endIndex, constraintCheckTime, not, deferrable);
        }
        return null;
    }

    private ConstraintCheckTime parseConstraintCheckTime() {
        ConstraintCheckTime constraintCheckTime = tryParseConstraintCheckTime();
        if (constraintCheckTime == null) {
            Token token = scan();
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a INITIALLY.");
        }
        return constraintCheckTime;
    }

    private ConstraintCheckTime tryParseConstraintCheckTime() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"INITIALLY".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (token1.isRegularIdentifier() && "DEFERRED".equalsIgnoreCase(token1.getContent())) {
            return new ConstraintCheckTime(token.getBeginIndex(), token1.getEndIndex(), true, false);
        } else if (token1.isImmediate()) {
            return new ConstraintCheckTime(token.getBeginIndex(), token1.getEndIndex(), false, true);
        } else {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a DEFERRED或IMMEDIATE.");
        }
    }

    private DropBehavior parseDropBehavior() {
        Token token = scan();
        if (!token.isRegularIdentifier()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a CASCADE或RESTRICT.");
        }
        boolean cascade;
        boolean restrict;
        String content = token.getContent();
        if ("CASCADE".equalsIgnoreCase(content)) {
            cascade = true;
            restrict = false;
        } else if ("RESTRICT".equalsIgnoreCase(content)) {
            cascade = false;
            restrict = true;
        } else {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a CASCADE或RESTRICT.");
        }
        return new DropBehavior(token.getBeginIndex(), token.getEndIndex(), cascade, restrict);
    }

    private AlterDomainAction parseAlterDomainAction() {
        AlterDomainAction alterDomainAction = tryParseSetDomainDefaultClause();
        if (alterDomainAction != null) {
            return alterDomainAction;
        }
        alterDomainAction = tryParseDropDomainDefaultClause();
        if (alterDomainAction != null) {
            return alterDomainAction;
        }
        alterDomainAction = tryParseAddDomainConstraintDefinition();
        if (alterDomainAction != null) {
            return alterDomainAction;
        }
        alterDomainAction = tryParseDropDomainDonstraintDefinition();
        if (alterDomainAction != null) {
            return alterDomainAction;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to alter domain statement.");
    }

    private SetDomainDefaultClause tryParseSetDomainDefaultClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            reset(markedIndex);
            return null;
        }
        DefaultClause defaultClause = parseDefaultClause();
        return new SetDomainDefaultClause(token.getBeginIndex(), defaultClause);
    }

    private DropDomainDefaultClause tryParseDropDomainDefaultClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (token1.isDefault()) {
            reset(markedIndex);
            return null;
        }
        return new DropDomainDefaultClause(token.getBeginIndex(), token1.getEndIndex());
    }

    private DropDomainConstraintDefinition tryParseDropDomainDonstraintDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isDrop()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (token1.isConstraint()) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName constraintName = parseSchemaQualifiedName();
        return new DropDomainConstraintDefinition(token.getBeginIndex(), constraintName);
    }

    private AddDomainConstraintDefinition tryParseAddDomainConstraintDefinition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAdd()) {
            reset(markedIndex);
            return null;
        }
        DomainConstraint domainConstraint = parseDomainConstraint();
        return new AddDomainConstraintDefinition(token.getBeginIndex(), domainConstraint);
    }

    private DomainConstraint parseDomainConstraint() {
        DomainConstraint domainConstraint = tryParseDomainConstraint();
        if (domainConstraint != null) {
            return domainConstraint;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to domain constraint.");
    }

    private DomainConstraint tryParseDomainConstraint() {
        ConstraintNameDefinition constraintNameDefinition = tryParseConstraintNameDefinition();
        CheckConstraintDefinition checkConstraintDefinition;
        ConstraintCharacteristics constraintCharacteristics;
        if (constraintNameDefinition != null) {
            checkConstraintDefinition = parseCheckConstraintDefinition();
            constraintCharacteristics = tryParseConstraintCharacteristics();
        } else {
            checkConstraintDefinition = tryParseCheckConstraintDefinition();
            if (checkConstraintDefinition == null) {
                constraintCharacteristics = null;
            } else {
                constraintCharacteristics = tryParseConstraintCharacteristics();
            }
        }
        if (checkConstraintDefinition == null) {
            return null;
        }
        return new DomainConstraint(constraintNameDefinition, checkConstraintDefinition, constraintCharacteristics);
    }

    private FromClause parseFromClause() {
        FromClause fromClause = tryParseFromClause();
        if (fromClause != null) {
            return fromClause;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a From子句.");
    }

    private FromClause tryParseFromClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isFrom()) {
            reset(markedIndex);
            return null;
        }
        TableReference tableReference = parseTableReference();
        List<TableReference> tableReferenceList = new ArrayList<>();
        tableReferenceList.add(tableReference);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            tableReference = parseTableReference();
            tableReferenceList.add(tableReference);
        }
        return new FromClause(token.getBeginIndex(), new TableReferenceList(tableReferenceList));
    }

    private TableReference parseTableReference() {
        TableReference tableReference = tryParseTableReference();
        if (tableReference != null) {
            return tableReference;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a table reference.");
    }

    private TableReference tryParseTableReference() {
        TablePrimaryOrJoinedTable tablePrimaryOrJoinedTable = tryParseTablePrimaryOrJoinedTable();
        if (tablePrimaryOrJoinedTable == null) {
            return null;
        }
        SampleClause sampleClause = tryParseSampleClause();
        return new TableReference(tablePrimaryOrJoinedTable, sampleClause);
    }

    private TablePrimaryOrJoinedTable tryParseTablePrimaryOrJoinedTable() {
        TablePrimaryOrJoinedTable tablePrimaryOrJoinedTable = tryParseTablePrimary();
        if (tablePrimaryOrJoinedTable == null) {
            tablePrimaryOrJoinedTable = tryParseJoinedTable();
        }
        return tablePrimaryOrJoinedTable;
    }

    private TablePrimary parseTablePrimary() {
        TablePrimary tablePrimary = tryParseTablePrimary();
        if (tablePrimary != null) {
            return tablePrimary;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to table primary.");
    }

    private TablePrimary tryParseTablePrimary() {
        TablePrimary tablePrimary = tryParseTableOrQueryNameTablePrimary();
        if (tablePrimary != null) {
            return tablePrimary;
        }
        tablePrimary = tryParseDerivedTableTablePrimary();
        if (tablePrimary != null) {
            return tablePrimary;
        }
        tablePrimary = tryParseLateralDerivedTableTablePrimary();
        if (tablePrimary != null) {
            return tablePrimary;
        }
        tablePrimary = tryParseCollectionDerivedTableTablePrimary();
        if (tablePrimary != null) {
            return tablePrimary;
        }
        tablePrimary = tryParseTableFunctionDerivedTableTablePrimary();
        if (tablePrimary != null) {
            return tablePrimary;
        }
        tablePrimary = tryParseOnlySpecTablePrimary();
        if (tablePrimary != null) {
            return tablePrimary;
        }
        tablePrimary = tryParseParenthesizedJoinedTableTablePrimary();
        return tablePrimary;
    }

    private TableOrQueryNameTablePrimary tryParseTableOrQueryNameTablePrimary() {
        TableOrQueryName tableOrQueryName = tryParseTableOrQueryName();
        if (tableOrQueryName == null) {
            return null;
        }
        int endIndex;
        Identifier correlationName;
        IdentifierChain derivedColumnList;
        int markedIndex = mark();
        Token token = scan();
        if (token.isAs()) {
            correlationName = parseIdentifier();
            int markedIndex1 = mark();
            Token token1 = scan();
            if (token1.isLeftParen()) {
                derivedColumnList = parseIdentifierChain();
                Token token2 = scan();
                if (!token2.isRightParen()) {
                    throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
                }
                endIndex = token2.getEndIndex();
            } else {
                reset(markedIndex1);
                derivedColumnList = null;
                endIndex = correlationName.getEndIndex();
            }
        } else {
            reset(markedIndex);
            correlationName = null;
            derivedColumnList = null;
            endIndex = tableOrQueryName.getEndIndex();
        }
        return new TableOrQueryNameTablePrimary(endIndex, tableOrQueryName, correlationName, derivedColumnList);
    }

    private TableOrQueryName parseTableOrQueryName() {
        TableOrQueryName tableOrQueryName = tryParseTableOrQueryName();
        if (tableOrQueryName != null) {
            return tableOrQueryName;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to table or query name.");
    }

    private TableOrQueryName tryParseTableOrQueryName() {
        LocalOrSchemaQualifiedName tableName = tryParseLocalOrSchemaQualifiedName();
        if (tableName != null) {
            SchemaName schemaName = tableName.getSchemaName();
            if (schemaName == null) {
                return tableName.getQualifiedIdentifier();
            }
            return tableName;
        }
        Identifier queryName = tryParseIdentifier();
        return queryName;
    }

    private DerivedTableTablePrimary tryParseDerivedTableTablePrimary() {
        Subquery derivedTable = tryParseSubquery();
        if (derivedTable == null) {
            return null;
        }
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAs()) {
            reset(markedIndex);
        }
        int endIndex;
        Identifier correlationName = parseIdentifier();
        IdentifierChain derivedColumnList;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isLeftParen()) {
            derivedColumnList = parseIdentifierChain();
            Token token2 = scan();
            if (!token2.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
            }
            endIndex = token2.getEndIndex();
        } else {
            reset(markedIndex1);
            derivedColumnList = null;
            endIndex = correlationName.getEndIndex();
        }
        return new DerivedTableTablePrimary(endIndex, derivedTable, correlationName, derivedColumnList);
    }

    private LateralDerivedTableTablePrimary tryParseLateralDerivedTableTablePrimary() {
        int markedIndex0 = mark();
        Token token0 = scan();
        if (!token0.isLateral()) {
            reset(markedIndex0);
            return null;
        }
        Subquery tableSubquery = parseSubquery();
        int markedIndex = mark();
        Token token = scan();
        if (!token.isAs()) {
            reset(markedIndex);
        }
        int endIndex;
        Identifier correlationName = parseIdentifier();
        IdentifierChain derivedColumnList;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isLeftParen()) {
            derivedColumnList = parseIdentifierChain();
            Token token2 = scan();
            if (!token2.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
            }
            endIndex = token2.getEndIndex();
        } else {
            reset(markedIndex1);
            derivedColumnList = null;
            endIndex = correlationName.getEndIndex();
        }
        return new LateralDerivedTableTablePrimary(
                token.getBeginIndex(),
                endIndex,
                tableSubquery,
                correlationName,
                derivedColumnList);
    }

    private CollectionDerivedTableTablePrimary tryParseCollectionDerivedTableTablePrimary() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isUnnest()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        CollectionValueExpression collectionValueExpression = parseCollectionValueExpression();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        boolean withOrdinality;
        int markedIndex3 = mark();
        Token token3 = scan();
        if (token3.isWith()) {
            Token token4 = scan();
            if (!token4.isRegularIdentifier() || !"ORDINALITY".equalsIgnoreCase(token4.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a ORDINALITY.");
            } else {
                withOrdinality = true;
            }
        } else {
            reset(markedIndex3);
            withOrdinality = false;
        }
        int markedIndex5 = mark();
        Token token5 = scan();
        if (!token5.isAs()) {
            reset(markedIndex5);
        }
        int endIndex;
        Identifier correlationName = parseIdentifier();
        IdentifierChain derivedColumnList;
        int markedIndex6 = mark();
        Token token6 = scan();
        if (token6.isLeftParen()) {
            derivedColumnList = parseIdentifierChain();
            Token token7 = scan();
            if (!token7.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token7.getBeginIndex(), "Missing a ).");
            }
            endIndex = token7.getEndIndex();
        } else {
            reset(markedIndex6);
            derivedColumnList = null;
            endIndex = correlationName.getEndIndex();
        }
        return new CollectionDerivedTableTablePrimary(
                token.getBeginIndex(),
                endIndex,
                collectionValueExpression,
                withOrdinality,
                correlationName,
                derivedColumnList);
    }

    private CollectionValueExpression parseCollectionValueExpression() {
        CollectionValueExpression collectionValueExpression = tryParseCollectionValueExpression();
        if (collectionValueExpression != null) {
            return collectionValueExpression;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to collection value expression.");
    }

    // <array value expression> | <multiset value expression>
    private CollectionValueExpression tryParseCollectionValueExpression() {
        ArrayValueExpression arrayValueExpression = tryParseArrayValueExpression();
        if (arrayValueExpression != null) {
            return arrayValueExpression;
        }
        MultisetValueExpression multisetValueExpression = tryParseMultisetValueExpression();
        if (multisetValueExpression != null) {
            return multisetValueExpression;
        }
        return null;
    }

    // <array concatenation> | <array factor>
    private ArrayValueExpression tryParseArrayValueExpression() {
        ValueExpressionPrimary arrayFactor = tryParseValueExpressionPrimary();
        if (arrayFactor == null) {
            return null;
        }
        List<ValueExpressionPrimary> arrayFactorList = new ArrayList<>();
        arrayFactorList.add(arrayFactor);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isConcatenationOperator()) {
                reset(markedIndex);
                break;
            }
            ValueExpressionPrimary arrayFactor1 = parseValueExpressionPrimary();
            arrayFactorList.add(arrayFactor1);
        }
        return new ArrayValueExpression(arrayFactorList);
    }

    private MultisetValueExpression parseMultisetValueExpression() {
        MultisetValueExpression multisetValueExpression = tryParseMultisetValueExpression();
        if (multisetValueExpression != null) {
            return multisetValueExpression;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to multiset value expression.");
    }

    //   <multiset term>
    // | <multiset value expression> MULTISET UNION [ ALL | DISTINCT ] <multiset term>
    // | <multiset value expression> MULTISET EXCEPT [ ALL | DISTINCT ] <multiset term>
    private MultisetValueExpression tryParseMultisetValueExpression() {
        MultisetValueExpression multisetValueExpression = tryParseMultisetTerm();
        if (multisetValueExpression == null) {
            return null;
        }
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isMultiset()) {
                reset(markedIndex);
                break;
            }
            final Token token1 = scan();
            if (!token1.isUnion() && !token1.isExcept()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a UNION or EXCEPT.");
            }
            boolean all = false;
            boolean distinct = false;
            int markedIndex2 = mark();
            Token token2 = scan();
            if (token2.isAll()) {
                all = true;
            } else if (token2.isDistinct()) {
                distinct = true;
            } else {
                reset(markedIndex2);
            }
            MultisetTerm multisetTerm = parseMultisetTerm();
            if (token1.isUnion()) {
                multisetValueExpression = new MultisetUnion(multisetValueExpression, all, distinct, multisetTerm);
            } else {
                multisetValueExpression = new MultisetExcept(multisetValueExpression, all, distinct, multisetTerm);
            }
        }
        return multisetValueExpression;
    }

    private MultisetTerm parseMultisetTerm() {
        MultisetTerm multisetTerm = tryParseMultisetTerm();
        if (multisetTerm != null) {
            return multisetTerm;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to multiset term.");
    }

    private MultisetTerm tryParseMultisetTerm() {
        MultisetTerm multisetTerm = tryParseMultisetPrimary();
        if (multisetTerm == null) {
            return null;
        }
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isMultiset()) {
                reset(markedIndex);
                break;
            }
            Token token1 = scan();
            if (!token1.isIntersect()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a INTERSECT.");
            }
            boolean all = false;
            boolean distinct = false;
            int markedIndex2 = mark();
            Token token2 = scan();
            if (token2.isAll()) {
                all = true;
            } else if (token2.isDistinct()) {
                distinct = true;
            } else {
                reset(markedIndex2);
            }
            MultisetPrimary multisetPrimary = parseMultisetPrimary();
            multisetTerm = new MultisetIntersect(multisetTerm, all, distinct, multisetPrimary);
        }
        return multisetTerm;
    }

    private MultisetPrimary parseMultisetPrimary() {
        MultisetPrimary multisetPrimary = tryParseMultisetPrimary();
        if (multisetPrimary != null) {
            return multisetPrimary;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to multiset primary.");
    }

    // <multiset value function> | <value expression primary>
    private MultisetPrimary tryParseMultisetPrimary() {
        MultisetSetFunction multisetValueFunction = tryParseMultisetSetFunction();
        if (multisetValueFunction != null) {
            return multisetValueFunction;
        }
        ValueExpressionPrimary valueExpressionPrimary = tryParseValueExpressionPrimary();
        if (valueExpressionPrimary != null) {
            return valueExpressionPrimary;
        }
        return null;
    }

    // SET <left paren> <multiset value expression> <right paren>
    private MultisetSetFunction tryParseMultisetSetFunction() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isSet()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        MultisetValueExpression multisetValueExpression = parseMultisetValueExpression();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new MultisetSetFunction(token.getBeginIndex(), token2.getEndIndex(), multisetValueExpression);
    }

    private TableFunctionDerivedTableTablePrimary tryParseTableFunctionDerivedTableTablePrimary() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isTable()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        CollectionValueExpression collectionValueExpression = parseCollectionValueExpression();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        int markedIndex5 = mark();
        Token token5 = scan();
        if (!token5.isAs()) {
            reset(markedIndex5);
        }
        int endIndex;
        Identifier correlationName = parseIdentifier();
        IdentifierChain derivedColumnList;
        int markedIndex6 = mark();
        Token token6 = scan();
        if (token6.isLeftParen()) {
            derivedColumnList = parseIdentifierChain();
            Token token7 = scan();
            if (!token7.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token7.getBeginIndex(), "Missing a ).");
            }
            endIndex = token7.getEndIndex();
        } else {
            reset(markedIndex6);
            derivedColumnList = null;
            endIndex = correlationName.getEndIndex();
        }
        return new TableFunctionDerivedTableTablePrimary(
                token.getBeginIndex(),
                endIndex,
                collectionValueExpression,
                correlationName,
                derivedColumnList);
    }

    private OnlySpecTablePrimary tryParseOnlySpecTablePrimary() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isOnly()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        TableOrQueryName tableOrQueryName = parseTableOrQueryName();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        int endIndex;
        Identifier correlationName;
        IdentifierChain derivedColumnList;
        int markedIndex3 = mark();
        Token token3 = scan();
        if (token3.isAs()) {
            correlationName = parseIdentifier();
            int markedIndex4 = mark();
            Token token4 = scan();
            if (token4.isLeftParen()) {
                derivedColumnList = parseIdentifierChain();
                Token token5 = scan();
                if (!token5.isRightParen()) {
                    throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing a ).");
                }
                endIndex = token5.getEndIndex();
            } else {
                reset(markedIndex4);
                derivedColumnList = null;
                endIndex = correlationName.getEndIndex();
            }
        } else {
            reset(markedIndex3);
            correlationName = null;
            derivedColumnList = null;
            endIndex = tableOrQueryName.getEndIndex();
        }
        return new OnlySpecTablePrimary(
                token.getBeginIndex(),
                endIndex,
                tableOrQueryName,
                correlationName,
                derivedColumnList);
    }

    private ParenthesizedJoinedTableTablePrimary tryParseParenthesizedJoinedTableTablePrimary() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        JoinedTable joinedTable = parseJoinedTable();
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new ParenthesizedJoinedTableTablePrimary(token.getBeginIndex(), token1.getEndIndex(), joinedTable);
    }

    private JoinedTable parseJoinedTable() {
        JoinedTable joinedTable = tryParseJoinedTable();
        if (joinedTable != null) {
            return joinedTable;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to joined table.");
    }

    private JoinedTable tryParseJoinedTable() {
        CrossJoin crossJoin = tryParseCrossJoin();
        if (crossJoin != null) {
            return crossJoin;
        }
        QualifiedJoin qualifiedJoin = tryParseQualifiedJoin();
        if (qualifiedJoin != null) {
            return qualifiedJoin;
        }
        NaturalJoin naturalJoin = tryParseNaturalJoin();
        if (naturalJoin != null) {
            return naturalJoin;
        }
        UnionJoin unionJoin = tryParseUnionJoin();
        if (unionJoin != null) {
            return unionJoin;
        }
        return null;
    }

    private CrossJoin parseCrossJoin() {
        CrossJoin crossJoin = tryParseCrossJoin();
        if (crossJoin != null) {
            return crossJoin;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to cross join.");
    }

    // <table reference> CROSS JOIN <table primary>
    private CrossJoin tryParseCrossJoin() {
        TableReference tableReference = tryParseTableReference();
        if (tableReference == null) {
            return null;
        }
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCross()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isJoin()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a JOIN.");
        }
        TablePrimary tablePrimary = parseTablePrimary();
        return new CrossJoin(tableReference, tablePrimary);
    }

    // <table reference> [ <join type> ] JOIN <table reference> <join specification>
    private QualifiedJoin parseQualifiedJoin() {
        QualifiedJoin qualifiedJoin = tryParseQualifiedJoin();
        if (qualifiedJoin != null) {
            return qualifiedJoin;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to qualified join.");
    }

    // <table reference> [ <join type> ] JOIN <table reference> <join specification>
    private QualifiedJoin tryParseQualifiedJoin() {
        int markedIndex = mark();
        TableReference tableReference0 = tryParseTableReference();
        if (tableReference0 == null) {
            return null;
        }
        JoinType joinType = tryParseJoinType();
        if (joinType != null) {
            Token token1 = scan();
            if (!token1.isJoin()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a JOIN.");
            }
        } else {
            Token token1 = scan();
            if (!token1.isJoin()) {
                reset(markedIndex);
                return null;
            }
        }
        TableReference tableReference1 = parseTableReference();
        JoinSpecification joinSpecification = parseJoinSpecification();
        return new QualifiedJoin(
                tableReference0,
                joinType,
                tableReference1,
                joinSpecification);
    }

    private JoinSpecification parseJoinSpecification() {
        JoinSpecification joinSpecification = tryParseJoinCondition();
        if (joinSpecification != null) {
            return joinSpecification;
        }
        joinSpecification = tryParseNamedColumnsJoin();
        if (joinSpecification != null) {
            return joinSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to join specification.");
    }

    private JoinCondition tryParseJoinCondition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isOn()) {
            reset(markedIndex);
            return null;
        }
        BooleanValueExpression searchCondition = parseBooleanValueExpression();
        return new JoinCondition(token.getBeginIndex(), searchCondition);
    }

    private NamedColumnsJoin tryParseNamedColumnsJoin() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isUsing()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        IdentifierList joinColumnList = parseIdentifierList();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new NamedColumnsJoin(token.getBeginIndex(), token2.getEndIndex(), joinColumnList);
    }

    private JoinType parseJoinType() {
        JoinType joinType = tryParseJoinType();
        if (joinType != null) {
            return joinType;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to join type.");
    }

    private JoinType tryParseJoinType() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isInner()) {
            return new JoinType(token.getBeginIndex(), token.getEndIndex(), true, false, false, false);
        }
        boolean leftOuter;
        boolean rightOuter;
        boolean fullOuter;
        if (token.isLeft()) {
            leftOuter = true;
            rightOuter = false;
            fullOuter = false;
        } else if (token.isRight()) {
            leftOuter = false;
            rightOuter = true;
            fullOuter = false;
        } else if (token.isFull()) {
            leftOuter = false;
            rightOuter = false;
            fullOuter = true;
        } else {
            reset(markedIndex);
            return null;
        }
        int markedIndex1 = mark();
        Token token1 = scan();
        if (!token1.isOuter()) {
            reset(markedIndex1);
        }
        return new JoinType(token.getBeginIndex(), token1.getEndIndex(), false, leftOuter, rightOuter, fullOuter);
    }

    private NaturalJoin parseNaturalJoin() {
        NaturalJoin naturalJoin = tryParseNaturalJoin();
        if (naturalJoin != null) {
            return naturalJoin;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to natural join.");
    }

    // <table reference> NATURAL [ <join type> ] JOIN <table primary>
    private NaturalJoin tryParseNaturalJoin() {
        int markedIndex = mark();
        TableReference tableReference = tryParseTableReference();
        if (tableReference == null) {
            return null;
        }
        Token token = scan();
        if (!token.isNatural()) {
            reset(markedIndex);
            return null;
        }
        JoinType joinType = tryParseJoinType();
        Token token1 = scan();
        if (!token1.isJoin()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a JOIN.");
        }
        TablePrimary tablePrimary = parseTablePrimary();
        return new NaturalJoin(
                tableReference,
                joinType,
                tablePrimary);
    }

    private UnionJoin parseUnionJoin() {
        UnionJoin unionJoin = tryParseUnionJoin();
        if (unionJoin != null) {
            return unionJoin;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to union join.");
    }

    // <table reference> UNION JOIN <table primary>
    private UnionJoin tryParseUnionJoin() {
        TableReference tableReference = tryParseTableReference();
        if (tableReference == null) {
            return null;
        }
        int markedIndex = mark();
        Token token = scan();
        if (!token.isUnion()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isJoin()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a JOIN.");
        }
        TablePrimary tablePrimary = parseTablePrimary();
        return new UnionJoin(tableReference, tablePrimary);
    }

    // TABLESAMPLE <sample method> <left paren> <sample percentage> <right paren> [ <repeatable clause> ]
    private SampleClause tryParseSampleClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() && !"TABLESAMPLE".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        SampleMethod sampleMethod = parseSampleMethod();
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        NumericValueExpression samplePercentage = parseNumericValueExpression();
        Token token2 = scan();
        if (token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        int endIndex = token2.getEndIndex();
        RepeatableClause repeatableClause = tryParseRepeatableClause();
        if (repeatableClause != null) {
            endIndex = repeatableClause.getEndIndex();
        }
        return new SampleClause(token.getBeginIndex(), endIndex, sampleMethod, samplePercentage, repeatableClause);
    }

    private SampleMethod parseSampleMethod() {
        Token token = scan();
        if (token.isRegularIdentifier() && "BERNOULLI".equalsIgnoreCase(token.getContent())) {
            return new SampleMethod(token.getBeginIndex(), token.getEndIndex(), true, false);
        }
        if (token.isSystem()) {
            return new SampleMethod(token.getBeginIndex(), token.getEndIndex(), false, true);
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a BERNOULLI o SYSTEM.");
    }

    private NumericValueExpression parseNumericValueExpression() {
        NumericValueExpression numericValueExpression = tryParseNumericValueExpression();
        if (numericValueExpression != null) {
            return numericValueExpression;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to numeric value expression.");
    }

    private NumericValueExpression tryParseNumericValueExpression() {
        NumericValueExpression numericValueExpression = tryParseTerm();
        if (numericValueExpression == null) {
            return null;
        }
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (token.isPlusSign()) {
                Term term = parseTerm();
                numericValueExpression = new PlusTerm(numericValueExpression, term);
            } else if (token.isMinusSign()) {
                Term term = parseTerm();
                numericValueExpression = new MinusTerm(numericValueExpression, term);
            } else {
                reset(markedIndex);
                break;
            }
        }
        return numericValueExpression;
    }

    private Term parseTerm() {
        Term term = parseFactor();
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (token.isAsterisk()) {
                Factor factor = parseFactor();
                term = new AsteriskFactor(term, factor);
            } else if (token.isSolidus()) {
                Factor factor = parseFactor();
                term = new SolidusFactor(term, factor);
            } else {
                reset(markedIndex);
                break;
            }
        }
        return term;
    }

    private Term tryParseTerm() {
        Term term = tryParseFactor();
        if (term == null) {
            return null;
        }
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (token.isAsterisk()) {
                Factor factor = parseFactor();
                term = new AsteriskFactor(term, factor);
            } else if (token.isSolidus()) {
                Factor factor = parseFactor();
                term = new SolidusFactor(term, factor);
            } else {
                reset(markedIndex);
                break;
            }
        }
        return term;
    }

    private Factor parseFactor() {
        Factor factor = tryParseFactor();
        if (factor != null) {
            return factor;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to factor.");
    }

    private Factor tryParseFactor() {
        Sign sign = tryParseSign();
        if (sign == null) {
            NumericPrimary numericPrimary = tryParseNumericPrimary();
            if (numericPrimary == null) {
                return null;
            }
            return new Factor(sign, numericPrimary);
        }
        NumericPrimary numericPrimary = parseNumericPrimary();
        return new Factor(sign, numericPrimary);
    }

    private NumericPrimary parseNumericPrimary() {
        NumericPrimary numericPrimary = tryParseNumericPrimary();
        if (numericPrimary != null) {
            return numericPrimary;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to numeric primary.");
    }

    private NumericPrimary tryParseNumericPrimary() {
        ValueExpressionPrimary valueExpressionPrimary = tryParseValueExpressionPrimary();
        if (valueExpressionPrimary != null) {
            return valueExpressionPrimary;
        }
        NumericValueFunction numericValueFunction = tryParseNumericValueFunction();
        if (numericValueFunction != null) {
            return numericValueFunction;
        }
        return null;
    }

    //   <position expression>
    // | <extract expression>
    // | <length expression>
    // | <cardinality expression>
    // | <absolute value expression>
    // | <modulus expression>
    // | <natural logarithm>
    // | <exponential function>
    // | <power function>
    // | <square root>
    // | <floor function>
    // | <ceiling function>
    // | <width bucket function>
    private NumericValueFunction tryParseNumericValueFunction() {
        PositionExpression positionExpression = tryParsePositionExpression();
        if (positionExpression != null) {
            return positionExpression;
        }
        ExtractExpression extractExpression = tryParseExtractExpression();
        if (extractExpression != null) {
            return extractExpression;
        }
        LengthExpression lengthExpression = tryParseLengthExpression();
        if (lengthExpression != null) {
            return lengthExpression;
        }
        CardinalityExpression cardinalityExpression = tryParseCardinalityExpression();
        if (cardinalityExpression != null) {
            return cardinalityExpression;
        }
        AbsoluteValueExpression absoluteValueExpression = tryParseAbsoluteValueExpression();
        if (absoluteValueExpression != null) {
            return absoluteValueExpression;
        }
        ModulusExpression modulusExpression = tryParseModulusExpression();
        if (modulusExpression != null) {
            return modulusExpression;
        }
        NaturalLogarithm naturalLogarithm = tryParseNaturalLogarithm();
        if (naturalLogarithm != null) {
            return naturalLogarithm;
        }
        ExponentialFunction exponentialFunction = tryParseExponentialFunction();
        if (exponentialFunction != null) {
            return exponentialFunction;
        }
        PowerFunction powerFunction = tryParsePowerFunction();
        if (powerFunction != null) {
            return powerFunction;
        }
        SquareRoot squareRoot = tryParseSquareRoot();
        if (squareRoot != null) {
            return squareRoot;
        }
        FloorFunction floorFunction = tryParseFloorFunction();
        if (floorFunction != null) {
            return floorFunction;
        }
        CeilingFunction ceilingFunction = tryParseCeilingFunction();
        if (ceilingFunction != null) {
            return ceilingFunction;
        }
        WidthBucketFunction widthBucketFunction = tryParseWidthBucketFunction();
        if (widthBucketFunction != null) {
            return widthBucketFunction;
        }
        return null;
    }

    // WIDTH_BUCKET <left paren> <width bucket operand> <comma> <width bucket bound 1> <comma> <width bucket bound 2> <comma> <width bucket count> <right paren>
    private WidthBucketFunction tryParseWidthBucketFunction() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"WIDTH_BUCKET".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        NumericValueExpression widthBucketOperand = parseNumericValueExpression();
        Token token2 = scan();
        if (!token2.isComma()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ,.");
        }
        NumericValueExpression widthBucketBound1 = parseNumericValueExpression();
        Token token3 = scan();
        if (!token3.isComma()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ,.");
        }
        NumericValueExpression widthBucketBound2 = parseNumericValueExpression();
        Token token4 = scan();
        if (!token4.isComma()) {
            throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a ,.");
        }
        NumericValueExpression widthBucketCount = parseNumericValueExpression();
        Token token5 = scan();
        if (!token5.isComma()) {
            throw new YuliaException(getSql(), getLineNumber(), token5.getBeginIndex(), "Missing a ).");
        }
        return new WidthBucketFunction(
                token.getBeginIndex(),
                token4.getEndIndex(),
                widthBucketOperand,
                widthBucketBound1,
                widthBucketBound2,
                widthBucketCount
        );
    }

    // { CEIL | CEILING } <left paren> <numeric value expression> <right paren>
    private CeilingFunction tryParseCeilingFunction() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"CEIL".equalsIgnoreCase(token.getContent()) && !"CEILING".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        NumericValueExpression numericValueExpression = parseNumericValueExpression();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new CeilingFunction(
                token.getBeginIndex(),
                token2.getEndIndex(),
                numericValueExpression
        );
    }

    // FLOOR <left paren> <numeric value expression> <right paren>
    private FloorFunction tryParseFloorFunction() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"FLOOR".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        NumericValueExpression numericValueExpression = parseNumericValueExpression();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new FloorFunction(
                token.getBeginIndex(),
                token2.getEndIndex(),
                numericValueExpression
        );
    }

    // SQRT <left paren> <numeric value expression> <right paren>
    private SquareRoot tryParseSquareRoot() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"SQRT".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        NumericValueExpression numericValueExpression = parseNumericValueExpression();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new SquareRoot(
                token.getBeginIndex(),
                token2.getEndIndex(),
                numericValueExpression
        );
    }

    // POWER <left paren> <numeric value expression base> <comma> <numeric value expression exponent> <right paren>
    private PowerFunction tryParsePowerFunction() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"POWER".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        NumericValueExpression numericValueExpressionBase = parseNumericValueExpression();
        Token token2 = scan();
        if (!token2.isComma()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ,.");
        }
        NumericValueExpression numericValueExpressionExponent = parseNumericValueExpression();
        Token token3 = scan();
        if (!token3.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
        }
        return new PowerFunction(
                token.getBeginIndex(),
                token3.getEndIndex(),
                numericValueExpressionBase,
                numericValueExpressionExponent
        );
    }

    // EXP <left paren> <numeric value expression> <right paren>
    private ExponentialFunction tryParseExponentialFunction() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"EXP".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        NumericValueExpression numericValueExpression = parseNumericValueExpression();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new ExponentialFunction(
                token.getBeginIndex(),
                token2.getEndIndex(),
                numericValueExpression
        );
    }

    // LN <left paren> <numeric value expression> <right paren>
    private NaturalLogarithm tryParseNaturalLogarithm() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"LN".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        NumericValueExpression numericValueExpression = parseNumericValueExpression();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new NaturalLogarithm(
                token.getBeginIndex(),
                token2.getEndIndex(),
                numericValueExpression
        );
    }

    // MOD <left paren> <numeric value expression dividend> <comma> <numeric value expression divisor> <right paren>
    private ModulusExpression tryParseModulusExpression() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"MOD".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        NumericValueExpression numericValueExpressionDividend = parseNumericValueExpression();
        Token token2 = scan();
        if (!token2.isComma()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ,.");
        }
        NumericValueExpression numericValueExpressionDivisor = parseNumericValueExpression();
        Token token3 = scan();
        if (!token3.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
        }
        return new ModulusExpression(
                token.getBeginIndex(),
                token3.getEndIndex(),
                numericValueExpressionDividend,
                numericValueExpressionDivisor
        );
    }

    // ABS <left paren> <numeric value expression> <right paren>
    private AbsoluteValueExpression tryParseAbsoluteValueExpression() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"ABS".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        NumericValueExpression numericValueExpression = parseNumericValueExpression();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new AbsoluteValueExpression(
                token.getBeginIndex(),
                token2.getEndIndex(),
                numericValueExpression
        );
    }

    // CARDINALITY <left paren> <collection value expression> <right paren>
    private CardinalityExpression tryParseCardinalityExpression() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"CARDINALITY".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        CollectionValueExpression collectionValueExpression = parseCollectionValueExpression();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new CardinalityExpression(
                token.getBeginIndex(),
                token2.getEndIndex(),
                collectionValueExpression
        );
    }

    private LengthExpression tryParseLengthExpression() {
        CharLengthExpression charLengthExpression = tryParseCharLengthExpression();
        if (charLengthExpression != null) {
            return charLengthExpression;
        }
        OctetLengthExpression octetLengthExpression = tryParseOctetLengthExpression();
        if (octetLengthExpression != null) {
            return octetLengthExpression;
        }
        return null;
    }

    // POSITION <left paren> <string value expression> IN <string value expression> [ USING <char length units> ] <right paren>
    private PositionExpression tryParsePositionExpression() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"POSITION".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        StringValueExpression stringValueExpression1 = parseStringValueExpression();
        Token token2 = scan();
        if (!token2.isIn()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a IN.");
        }
        StringValueExpression stringValueExpression2 = parseStringValueExpression();
        CharLengthUnits charLengthUnits = null;
        int markedIndex3 = mark();
        Token token3 = scan();
        if (token3.isUsing()) {
            charLengthUnits = parseCharLengthUnits();
        } else {
            reset(markedIndex3);
        }
        Token token4 = scan();
        if (!token4.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a ).");
        }
        return new PositionExpression(
                token.getBeginIndex(),
                token4.getEndIndex(),
                stringValueExpression1,
                stringValueExpression2,
                charLengthUnits
        );
    }

    // EXTRACT <left paren> <extract field> FROM <extract source> <right paren>
    private ExtractExpression tryParseExtractExpression() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"EXTRACT".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        ExtractField extractField = parseExtractField();
        Token token2 = scan();
        if (!token2.isFrom()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a FROM.");
        }
        ExtractSource extractSource = parseExtractSource();
        Token token3 = scan();
        if (!token3.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
        }
        return new ExtractExpression(
                token.getBeginIndex(),
                token3.getEndIndex(),
                extractField,
                extractSource
        );
    }

    // <primary datetime field> | <time zone field>
    private ExtractField parseExtractField() {
        PrimaryDatetimeField primaryDatetimeField = tryParsePrimaryDatetimeField();
        if (primaryDatetimeField != null) {
            return primaryDatetimeField;
        }
        TimeZoneField timeZoneField = tryParseTimeZoneField();
        if (timeZoneField != null) {
            return timeZoneField;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to extract field.");
    }

    // TIMEZONE_HOUR | TIMEZONE_MINUTE
    private TimeZoneField tryParseTimeZoneField() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isTimezoneHour()) {
            return new TimeZoneField(token.getBeginIndex(), token.getEndIndex(), true, false);
        }
        if (token.isTimezoneMinute()) {
            return new TimeZoneField(token.getBeginIndex(), token.getEndIndex(), false, true);
        }
        reset(markedIndex);
        return null;
    }

    // <datetime value expression> | <interval value expression>
    private ExtractSource parseExtractSource() {
        DatetimeValueExpression datetimeValueExpression = tryParseDatetimeValueExpression();
        if (datetimeValueExpression != null) {
            return datetimeValueExpression;
        }
        IntervalValueExpression intervalValueExpression = tryParseIntervalValueExpression();
        if (intervalValueExpression != null) {
            return intervalValueExpression;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to extract source.");
    }

    private CharLengthExpression parseCharLengthExpression() {
        CharLengthExpression charLengthExpression = tryParseCharLengthExpression();
        if (charLengthExpression != null) {
            return charLengthExpression;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to char length expression.");
    }

    // { CHAR_LENGTH | CHARACTER_LENGTH } <left paren> <string value expression> [ USING <char length units> ] <right paren>
    private CharLengthExpression tryParseCharLengthExpression() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && ("CHAR_LENGTH".equalsIgnoreCase(token.getContent()) || "CHARACTER_LENGTH".equalsIgnoreCase(token.getContent()))) {
            Token token1 = scan();
            if (!token1.isLeftParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
            }
            StringValueExpression stringValueExpression = parseStringValueExpression();
            CharLengthUnits charLengthUnits = null;
            Token token2 = scan();
            if (token2.isUsing()) {
                charLengthUnits = parseCharLengthUnits();
                token2 = scan();
            }
            if (!token2.isRightParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
            }
            return new CharLengthExpression(
                    token.getBeginIndex(),
                    token2.getEndIndex(),
                    stringValueExpression,
                    charLengthUnits
            );
        }
        reset(markedIndex);
        return  null;
    }

    // OCTET_LENGTH <left paren> <string value expression> <right paren>
    private OctetLengthExpression tryParseOctetLengthExpression() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"OCTET_LENGTH".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        StringValueExpression stringValueExpression = parseStringValueExpression();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new OctetLengthExpression(token.getBeginIndex(), token2.getEndIndex(), stringValueExpression);
    }

    private StringValueExpression parseStringValueExpression() {
        StringValueExpression stringValueExpression = tryParseStringValueExpression();
        if (stringValueExpression != null) {
            return stringValueExpression;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to string value expression.");
    }

    private StringValueExpression tryParseStringValueExpression() {
        StringValueExpression stringValueExpression = tryParseStringFactor();
        if (stringValueExpression == null) {
            return null;
        }
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isConcatenationOperator()) {
                reset(markedIndex);
                break;
            }
            StringFactor stringFactor = parseStringFactor();
            stringValueExpression = new StringConcatenation(stringValueExpression, stringFactor);
        }
        return stringValueExpression;
    }

    private StringFactor parseStringFactor() {
        StringFactor stringFactor = tryParseStringFactor();
        if (stringFactor != null) {
            return stringFactor;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to string factor.");
    }

    private StringFactor tryParseStringFactor() {
        StringPrimary stringPrimary = tryParseStringPrimary();
        if (stringPrimary == null) {
            return null;
        }
        CollateClause collateClause = tryParseCollateClause();
        return new StringFactor(stringPrimary, collateClause);
    }

    // <value expression primary> | <string value function>
    private StringPrimary tryParseStringPrimary() {
        ValueExpressionPrimary valueExpressionPrimary = tryParseValueExpressionPrimary();
        if (valueExpressionPrimary != null) {
            return valueExpressionPrimary;
        }
        StringValueFunction stringValueFunction = tryParseStringValueFunction();
        if (stringValueFunction != null) {
            return stringValueFunction;
        }
        return null;
    }

    //   <substring function>
    // | <regular expression substring function>
    // | <fold>
    // | <transcoding>
    // | <character transliteration>
    // | <trim function>
    // | <overlay function>
    // | <normalize function>
    // | <specific type method>
    private StringValueFunction tryParseStringValueFunction() {
        // SUBSTRING <left paren> <character value expression> FROM <start position> [ FOR <string length> ] [ USING <char length units> ] <right paren>
        // SUBSTRING <left paren> <character value expression> SIMILAR <character value expression> ESCAPE <escape character> <right paren>
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier() && "SUBSTRING".equalsIgnoreCase(token.getContent())) {
            Token token1 = scan();
            if (!token1.isLeftParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
            }
            StringValueExpression stringValueExpression = parseStringValueExpression();
            Token token2 = scan();
            if (token2.isFrom()) {
                NumericValueExpression startPosition = parseNumericValueExpression();
                NumericValueExpression stringLength = null;
                Token token3 = scan();
                if (token3.isFor()) {
                    stringLength = parseNumericValueExpression();
                    token3 = scan();
                }
                CharLengthUnits charLengthUnits = null;
                if (token3.isUsing()) {
                    charLengthUnits = parseCharLengthUnits();
                    token3 = scan();
                }
                if (!token3.isRightParen()) {
                    throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
                }
                return new SubstringFunction(
                        token.getBeginIndex(),
                        token3.getEndIndex(),
                        stringValueExpression,
                        startPosition,
                        stringLength,
                        charLengthUnits
                );
            } else if (token2.isSimilar()) {
                StringValueExpression stringValueExpression2 = parseStringValueExpression();
                Token token3 = scan();
                if (!token3.isEscape()) {
                    throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ESCAPE.");
                }
                EscapeCharacter escapeCharacter = parseEscapeCharacter();
                Token token4 = scan();
                if (!token4.isRightParen()) {
                    throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a ).");
                }
                return new RegularExpressionSubstringFunction(
                        token.getBeginIndex(),
                        token4.getEndIndex(),
                        stringValueExpression,
                        stringValueExpression2,
                        escapeCharacter
                );
            } else {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a FROM or SIMILAR.");
            }
        }
        reset(markedIndex);
        Fold fold = tryParseFold();
        if (fold != null) {
            return fold;
        }
        Transcoding transcoding = tryParseTranscoding();
        if (transcoding != null) {
            return transcoding;
        }
        CharacterTransliteration characterTransliteration = tryParseCharacterTransliteration();
        if (characterTransliteration != null) {
            return characterTransliteration;
        }
        TrimFunction trimFunction = tryParseTrimFunction();
        if (trimFunction != null) {
            return trimFunction;
        }
        OverlayFunction overlayFunction = tryParseOverlayFunction();
        if (overlayFunction != null) {
            return overlayFunction;
        }
        NormalizeFunction normalizeFunction = tryParseNormalizeFunction();
        if (normalizeFunction != null) {
            return normalizeFunction;
        }
        SpecificTypeMethod specificTypeMethod = tryParseSpecificTypeMethod();
        if (specificTypeMethod != null) {
            return specificTypeMethod;
        }
        return null;
    }

    // <user-defined type value expression> <period> SPECIFICTYPE
    private SpecificTypeMethod tryParseSpecificTypeMethod() {
        ValueExpressionPrimary userDefinedTypeValueExpression = tryParseValueExpressionPrimary();
        if (userDefinedTypeValueExpression == null) {
            return null;
        }
        Token token = scan();
        if (!token.isPeriod()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a ..");
        }
        Token token1 = scan();
        if (!token1.isSpecifictype()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SPECIFICTYPE.");
        }
        return new SpecificTypeMethod(
                token1.getEndIndex(),
                userDefinedTypeValueExpression
        );
    }

    // NORMALIZE <left paren> <character value expression> <right paren>
    private NormalizeFunction tryParseNormalizeFunction() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"NORMALIZE".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        StringValueExpression characterValueExpression = parseStringValueExpression();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new NormalizeFunction(
                token.getBeginIndex(),
                token2.getEndIndex(),
                characterValueExpression
        );
    }

    // OVERLAY
    // <left paren>
    // <character value expression>
    // PLACING <character value expression>
    // FROM <start position>
    // [ FOR <string length> ]
    // [ USING <char length units> ]
    // <right paren>
    private OverlayFunction tryParseOverlayFunction() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"OVERLAY".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        StringValueExpression characterValueExpression1 = parseStringValueExpression();
        Token token2 = scan();
        if (!token2.isRegularIdentifier() || !"PLACING".equalsIgnoreCase(token2.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a PLACING.");
        }
        StringValueExpression characterValueExpression2 = parseStringValueExpression();
        Token token3 = scan();
        if (!token3.isRegularIdentifier() || !"FROM".equalsIgnoreCase(token3.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a FROM.");
        }
        NumericValueExpression startPosition = parseNumericValueExpression();
        NumericValueExpression stringLength = null;
        Token token4 = scan();
        if (token4.isFor()) {
            stringLength = parseNumericValueExpression();
            token4 = scan();
        }
        CharLengthUnits charLengthUnits = null;
        if (token4.isUsing()) {
            charLengthUnits = parseCharLengthUnits();
            token4 = scan();
        }
        if (!token4.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token4.getBeginIndex(), "Missing a ).");
        }
        return new OverlayFunction(
                token.getBeginIndex(),
                token4.getEndIndex(),
                characterValueExpression1,
                characterValueExpression2,
                startPosition,
                stringLength,
                charLengthUnits
        );
    }

    // TRIM <left paren> <trim operands> <right paren>
    private TrimFunction tryParseTrimFunction() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"TRIM".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        TrimOperands trimOperands = parseTrimOperands();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new TrimFunction(
                token.getBeginIndex(),
                token2.getEndIndex(),
                trimOperands
        );
    }

    // [ [ <trim specification> ] [ <trim character> ] FROM ] <trim source>
    private TrimOperands parseTrimOperands() {
        TrimSpecification trimSpecification = tryParseTrimSpecification();
        StringValueExpression trimCharacter = tryParseStringValueExpression();
        int markedIndex = mark();
        Token token = scan();
        if (!token.isFrom()) {
            if (trimSpecification != null || trimCharacter != null) {
                throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a FROM.");
            } else {
                reset(markedIndex);
            }
        }
        int beginIndex = token.getBeginIndex();
        if (trimSpecification != null) {
            beginIndex = trimSpecification.getBeginIndex();
        } else {
            if (trimCharacter != null) {
                beginIndex = trimCharacter.getBeginIndex();
            }
        }
        StringValueExpression trimSource = parseStringValueExpression();
        return new TrimOperands(
                beginIndex,
                trimSource.getEndIndex(),
                trimSpecification,
                trimCharacter,
                trimSource
        );
    }

    // LEADING | TRAILING | BOTH
    private TrimSpecification tryParseTrimSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isLeading()) {
            return new TrimSpecification(token.getBeginIndex(), token.getEndIndex(), true, false, false);
        }
        if (token.isTrailing()) {
            return new TrimSpecification(token.getBeginIndex(), token.getEndIndex(), false, true, false);
        }
        if (token.isBoth()) {
            return new TrimSpecification(token.getBeginIndex(), token.getEndIndex(), false, false, true);
        }
        reset(markedIndex);
        return null;
    }

    // TRANSLATE <left paren> <character value expression> USING <transliteration name> <right paren>
    private CharacterTransliteration tryParseCharacterTransliteration() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"TRANSLATE".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        StringValueExpression characterValueExpression = parseStringValueExpression();
        Token token2 = scan();
        if (!token2.isUsing()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a USING.");
        }
        SchemaQualifiedName transliterationName = parseSchemaQualifiedName();
        Token token3 = scan();
        if (!token3.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
        }
        return new CharacterTransliteration(
                token.getBeginIndex(),
                token3.getEndIndex(),
                characterValueExpression,
                transliterationName
        );
    }

    // CONVERT <left paren> <character value expression> USING <transcoding name> <right paren>
    private Transcoding tryParseTranscoding() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"CONVERT".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        StringValueExpression characterValueExpression = parseStringValueExpression();
        Token token2 = scan();
        if (!token2.isUsing()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a USING.");
        }
        SchemaQualifiedName transcodingName = parseSchemaQualifiedName();
        Token token3 = scan();
        if (!token3.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
        }
        return new Transcoding(
                token.getBeginIndex(),
                token3.getEndIndex(),
                characterValueExpression,
                transcodingName
        );
    }

    // { UPPER | LOWER } <left paren> <character value expression> <right paren>
    private Fold tryParseFold() {
        boolean upper = false;
        boolean lower = false;
        int markedIndex = mark();
        Token token = scan();
        if (token.isRegularIdentifier()) {
            if ("UPPER".equalsIgnoreCase(token.getContent())) {
                upper = true;
            } else if ("".equalsIgnoreCase(token.getContent())) {
                lower = true;
            } else {
                reset(markedIndex);
                return null;
            }
        } else {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        StringValueExpression characterValueExpression = parseStringValueExpression();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new Fold(
                token.getBeginIndex(),
                token2.getEndIndex(),
                upper,
                lower,
                characterValueExpression
        );
    }

    private EscapeCharacter parseEscapeCharacter() {
        EscapeCharacter escapeCharacter = tryParseEscapeCharacter();
        if (escapeCharacter != null) {
            return escapeCharacter;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to escape character.");
    }

    private EscapeCharacter tryParseEscapeCharacter() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isStringLiteral()) {
            reset(markedIndex);
            return null;
        }
        return new EscapeCharacter(token.getBeginIndex(), token.getEndIndex(), token.getContent());
    }

    private RepeatableClause tryParseRepeatableClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRegularIdentifier() || !"REPEATABLE".equalsIgnoreCase(token.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        NumericValueExpression repeatArgument = parseNumericValueExpression();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new RepeatableClause(token.getBeginIndex(), token2.getEndIndex(), repeatArgument);
    }

    private ValueExpression parseValueExpression() {
        ValueExpression valueExpression = tryParseValueExpression();
        if (valueExpression != null) {
            return valueExpression;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to value expression.");
    }

    private ValueExpression tryParseValueExpression() {
        CommonValueExpression commonValueExpression = tryParseCommonValueExpression();
        if (commonValueExpression != null) {
            return commonValueExpression;
        }
        BooleanValueExpression booleanValueExpression = tryParseBooleanValueExpression();
        if (booleanValueExpression != null) {
            return booleanValueExpression;
        }
        return tryParseRowValueExpression();
    }

    //   <numeric value expression>
    // | <string value expression>
    // | <datetime value expression>
    // | <interval value expression>
    // | <user-defined type value expression>
    // | <reference value expression>
    // | <collection value expression>
    private CommonValueExpression tryParseCommonValueExpression() {
        NumericValueExpression numericValueExpression = tryParseNumericValueExpression();
        if (numericValueExpression != null) {
            return numericValueExpression;
        }
        StringValueExpression stringValueExpression = tryParseStringValueExpression();
        if (stringValueExpression != null) {
            return stringValueExpression;
        }
        DatetimeValueExpression datetimeValueExpression = tryParseDatetimeValueExpression();
        if (datetimeValueExpression != null) {
            return datetimeValueExpression;
        }
        IntervalValueExpression intervalValueExpression = tryParseIntervalValueExpression();
        if (intervalValueExpression != null) {
            return intervalValueExpression;
        }
        ValueExpressionPrimary valueExpressionPrimary = tryParseValueExpressionPrimary();
        if (valueExpressionPrimary != null) {
            return valueExpressionPrimary;
        }
        CollectionValueExpression collectionValueExpression = tryParseCollectionValueExpression();
        if (collectionValueExpression != null) {
            return collectionValueExpression;
        }
        return null;
    }

    private BooleanValueExpression parseBooleanValueExpression() {
        BooleanValueExpression booleanValueExpression = tryParseBooleanValueExpression();
        if (booleanValueExpression != null) {
            return booleanValueExpression;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to boolean value expression.");
    }

    private BooleanValueExpression tryParseBooleanValueExpression() {
        BooleanTerm booleanTerm = tryParseBooleanTerm();
        if (booleanTerm == null) {
            return null;
        }
        List<BooleanTerm> booleanTermList = new ArrayList<>();
        booleanTermList.add(booleanTerm);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isOr()) {
                reset(markedIndex);
                break;
            }
            BooleanTerm booleanTerm1 = parseBooleanTerm();
            booleanTermList.add(booleanTerm1);
        }
        return new BooleanValueExpression(booleanTermList);
    }

    private BooleanTerm parseBooleanTerm() {
        BooleanTerm booleanTerm = tryParseBooleanTerm();
        if (booleanTerm != null) {
            return booleanTerm;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to boolean term.");
    }

    private BooleanTerm tryParseBooleanTerm() {
        BooleanFactor booleanFactor = tryParseBooleanFactor();
        if (booleanFactor == null) {
            return null;
        }
        List<BooleanFactor> booleanFactorList = new ArrayList<>();
        booleanFactorList.add(booleanFactor);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isAnd()) {
                reset(markedIndex);
                break;
            }
            BooleanFactor booleanFactor1 = parseBooleanFactor();
            booleanFactorList.add(booleanFactor1);
        }
        return new BooleanTerm(booleanFactorList);
    }

    private BooleanFactor parseBooleanFactor() {
        BooleanFactor booleanFactor = tryParseBooleanFactor();
        if (booleanFactor != null) {
            return booleanFactor;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to boolean factor.");
    }

    private BooleanFactor tryParseBooleanFactor() {
        boolean not = false;
        int markedIndex = mark();
        Token token = scan();
        if (!token.isNot()) {
            reset(markedIndex);
        }
        BooleanTest booleanTest;
        if (not == true) {
            booleanTest = parseBooleanTest();
        } else {
            booleanTest = tryParseBooleanTest();
            if (booleanTest == null) {
                return null;
            }
        }
        return new BooleanFactor(token.getBeginIndex(), not, booleanTest);
    }

    private BooleanTest parseBooleanTest() {
        BooleanTest booleanTest = tryParseBooleanTest();
        if (booleanTest != null) {
            return booleanTest;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to boolean test.");
    }

    // <boolean primary> [ IS [ NOT ] <truth value> ]
    private BooleanTest tryParseBooleanTest() {
        BooleanPrimary booleanPrimary = tryParseBooleanPrimary();
        if (booleanPrimary == null) {
            return null;
        }
        int endIndex = booleanPrimary.getEndIndex();
        boolean not = false;
        TruthValue truthValue = null;
        int markedIndex = mark();
        Token token = scan();
        if (!token.isIs()) {
            reset(markedIndex);
        } else {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isNot()) {
                reset(markedIndex1);
            } else {
                not = true;
            }
            truthValue = parseTruthValue();
            endIndex = truthValue.getEndIndex();
        }
        return new BooleanTest(endIndex, booleanPrimary, not, truthValue);
    }

    // <predicate> | <boolean predicand>
    private BooleanPrimary tryParseBooleanPrimary() {
        Predicate predicate = tryParsePredicate();
        if (predicate != null) {
            return predicate;
        }
        return tryParseBooleanPredicand();
    }

    //   <comparison predicate>
    // | <between predicate>
    // | <in predicate>
    // | <like predicate>
    // | <similar predicate>
    // | <null predicate>
    // | <quantified comparison predicate>
    // | <exists predicate>
    // | <unique predicate>
    // | <normalized predicate>
    // | <match predicate>
    // | <overlaps predicate>
    // | <distinct predicate>
    // | <member predicate>
    // | <submultiset predicate>
    // | <set predicate>
    // | <type predicate>
    private Predicate tryParsePredicate() {
        int markedIndex = mark();
        RowValuePredicand rowValuePredicand = tryParseRowValuePredicand();
        if (rowValuePredicand != null) {
            ComparisonPredicate comparisonPredicate = tryParseComparisonPredicate(rowValuePredicand);
            if (comparisonPredicate != null) {
                return comparisonPredicate;
            }
            InPredicate inPredicate = tryParseInPredicate(rowValuePredicand);
            if (inPredicate != null) {
                return inPredicate;
            }
            LikePredicate likePredicate = tryParseLikePredicate(rowValuePredicand);
            if (likePredicate != null) {
                return likePredicate;
            }
            SimilarPredicate similarPredicate = tryParseSimilarPredicate(rowValuePredicand);
            if (similarPredicate != null) {
                return similarPredicate;
            }
            NullPredicate nullPredicate = tryParseNullPredicate(rowValuePredicand);
            if (nullPredicate != null) {
                return nullPredicate;
            }
            QuantifiedComparisonPredicate quantifiedComparisonPredicate
                    = tryParseQuantifiedComparisonPredicate(rowValuePredicand);
            if (quantifiedComparisonPredicate != null) {
                return quantifiedComparisonPredicate;
            }
            MatchPredicate matchPredicate = tryParseMatchPredicate(rowValuePredicand);
            if (matchPredicate != null) {
                return matchPredicate;
            }
            OverlapsPredicate overlapsPredicate = tryParseOverlapsPredicate(rowValuePredicand);
            if (overlapsPredicate != null) {
                return overlapsPredicate;
            }
            DistinctPredicate distinctPredicate = tryParseDistinctPredicate(rowValuePredicand);
            if (distinctPredicate != null) {
                return distinctPredicate;
            }
            MemberPredicate memberPredicate = tryParseMemberPredicate(rowValuePredicand);
            if (memberPredicate != null) {
                return memberPredicate;
            }
            SubmultisetPredicate submultisetPredicate = tryParseSubmultisetPredicate(rowValuePredicand);
            if (submultisetPredicate != null) {
                return submultisetPredicate;
            }
            SetPredicate setPredicate = tryParseSetPredicate(rowValuePredicand);
            if (setPredicate != null) {
                return setPredicate;
            }
            TypePredicate typePredicate = tryParseTypePredicate(rowValuePredicand);
            if (typePredicate != null) {
                return typePredicate;
            }
        }
        reset(markedIndex);
        BetweenPredicate betweenPredicate = tryParseBetweenPredicate();
        if (betweenPredicate != null) {
            return betweenPredicate;
        }
        ExistsPredicate existsPredicate = tryParseExistsPredicate();
        if (existsPredicate != null) {
            return existsPredicate;
        }
        UniquePredicate uniquePredicate = tryParseUniquePredicate();
        if (uniquePredicate != null) {
            return uniquePredicate;
        }
        NormalizedPredicate normalizedPredicate = tryParseNormalizedPredicate();
        if (normalizedPredicate != null) {
            return normalizedPredicate;
        }
        return null;
    }

    // <row value predicand> <type predicate part 2>
    private TypePredicate tryParseTypePredicate(RowValuePredicand rowValuePredicand) {
        TypePredicatePart2 typePredicatePart2 = tryParseTypePredicatePart2();
        if (typePredicatePart2 == null) {
            return null;
        }
        return new TypePredicate(rowValuePredicand, typePredicatePart2);
    }

    // IS [ NOT ] OF <left paren> <type list> <right paren>
    private TypePredicatePart2 tryParseTypePredicatePart2() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isIs()) {
            reset(markedIndex);
            return null;
        }
        boolean not = false;
        Token token1 = scan();
        if (token1.isNot()) {
            not = true;
            token1 = scan();
        }
        if (!token1.isOf()) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a (.");
        }
        TypeList typeList = parseTypeList();
        Token token3 = scan();
        if (!token3.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
        }
        return new TypePredicatePart2(
                token.getBeginIndex(),
                token3.getEndIndex(),
                not,
                typeList
        );
    }

    // <user-defined type specification> [ { <comma> <user-defined type specification> }... ]
    private TypeList parseTypeList() {
        UserDefinedTypeSpecification userDefinedTypeSpecification = parseUserDefinedTypeSpecification();
        List<UserDefinedTypeSpecification> typeList = new ArrayList();
        typeList.add(userDefinedTypeSpecification);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isComma()) {
                reset(markedIndex);
                break;
            }
            UserDefinedTypeSpecification userDefinedTypeSpecification1 = parseUserDefinedTypeSpecification();
            typeList.add(userDefinedTypeSpecification1);
        }
        return new TypeList(typeList);
    }

    private UserDefinedTypeSpecification parseUserDefinedTypeSpecification() {
        UserDefinedTypeSpecification userDefinedTypeSpecification = tryParseUserDefinedTypeSpecification();
        if (userDefinedTypeSpecification != null) {
            return userDefinedTypeSpecification;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to user-defined type specification.");
    }

    private UserDefinedTypeSpecification tryParseUserDefinedTypeSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isOnly()) {
            SchemaQualifiedName pathResolvedUserDefinedTypeName = parseSchemaQualifiedName();
            return new UserDefinedTypeSpecification(token.getBeginIndex(), pathResolvedUserDefinedTypeName);
        }
        reset(markedIndex);
        SchemaQualifiedName pathResolvedUserDefinedTypeName = tryParseSchemaQualifiedName();
        if (pathResolvedUserDefinedTypeName == null) {
            return null;
        }
        return new UserDefinedTypeSpecification(pathResolvedUserDefinedTypeName);
    }

    // <row value predicand> <set predicate part 2>
    private SetPredicate tryParseSetPredicate(RowValuePredicand rowValuePredicand) {
        SetPredicatePart2 setPredicatePart2 = tryParseSetPredicatePart2();
        if (setPredicatePart2 == null) {
            return null;
        }
        return new SetPredicate(rowValuePredicand, setPredicatePart2);
    }

    // IS [ NOT ] A SET
    private SetPredicatePart2 tryParseSetPredicatePart2() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isIs()) {
            reset(markedIndex);
            return null;
        }
        boolean not = false;
        Token token1 = scan();
        if (!token1.isNot()) {
            not = true;
            token1 = scan();
        }
        if (!token1.isRegularIdentifier() || !"A".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isSet()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a SET.");
        }
        return new SetPredicatePart2(token.getBeginIndex(), token2.getEndIndex(), not);
    }

    // <row value predicand> <submultiset predicate part 2>
    private SubmultisetPredicate tryParseSubmultisetPredicate(RowValuePredicand rowValuePredicand) {
        SubmultisetPredicatePart2 submultisetPredicatePart2 = tryParseSubmultisetPredicatePart2();
        if (submultisetPredicatePart2 == null) {
            return null;
        }
        return new SubmultisetPredicate(rowValuePredicand, submultisetPredicatePart2);
    }

    // [ NOT ] SUBMULTISET [ OF ] <multiset value expression>
    private SubmultisetPredicatePart2 tryParseSubmultisetPredicatePart2() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isNot() && !token.isMember()) {
            reset(markedIndex);
            return null;
        }
        boolean not = false;
        if (token.isNot()) {
            not = true;
            token = scan();
        }
        if (!token.isSubmultiset()) {
            reset(markedIndex);
            return null;
        }
        boolean of = false;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isOf()) {
            of = true;
        } else {
            reset(markedIndex1);
        }
        MultisetValueExpression multisetValueExpression = tryParseMultisetValueExpression();
        return new SubmultisetPredicatePart2(
                token.getBeginIndex(),
                not,
                of,
                multisetValueExpression
        );
    }

    // <row value predicand> <member predicate part 2>
    private MemberPredicate tryParseMemberPredicate(RowValuePredicand rowValuePredicand) {
        MemberPredicatePart2 memberPredicatePart2 = tryParseMemberPredicatePart2();
        if (memberPredicatePart2 == null) {
            return null;
        }
        return new MemberPredicate(rowValuePredicand, memberPredicatePart2);
    }

    // [ NOT ] MEMBER [ OF ] <multiset value expression>
    private MemberPredicatePart2 tryParseMemberPredicatePart2() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isNot() && !token.isMember()) {
            reset(markedIndex);
            return null;
        }
        boolean not = false;
        if (token.isNot()) {
            not = true;
            token = scan();
        }
        if (!token.isMember()) {
            reset(markedIndex);
            return null;
        }
        boolean of = false;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isOf()) {
            of = true;
        } else {
            reset(markedIndex1);
        }
        MultisetValueExpression multisetValueExpression = tryParseMultisetValueExpression();
        return new MemberPredicatePart2(
                token.getBeginIndex(),
                not,
                of,
                multisetValueExpression
        );
    }

    // <row value predicand> <distinct predicate part 2>
    private DistinctPredicate tryParseDistinctPredicate(RowValuePredicand rowValuePredicand) {
        DistinctPredicatePart2 distinctPredicatePart2 = tryParseDistinctPredicatePart2();
        if (distinctPredicatePart2 == null) {
            return null;
        }
        return new DistinctPredicate(rowValuePredicand, distinctPredicatePart2);
    }

    // IS DISTINCT FROM <row value predicand>
    private DistinctPredicatePart2 tryParseDistinctPredicatePart2() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isIs()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isDistinct()) {
            reset(markedIndex);
            return null;
        }
        Token token2 = scan();
        if (!token2.isFrom()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a FROM.");
        }
        RowValuePredicand rowValuePredicand = parseRowValuePredicand();
        return new DistinctPredicatePart2(
                token.getBeginIndex(),
                rowValuePredicand
        );
    }

    // <overlaps predicate part 1> <overlaps predicate part 2>
    private OverlapsPredicate tryParseOverlapsPredicate(RowValuePredicand overlapsPredicatePart1) {
        OverlapsPredicatePart2 overlapsPredicatePart2 = tryParseOverlapsPredicatePart2();
        if (overlapsPredicatePart2 == null) {
            return null;
        }
        return new OverlapsPredicate(overlapsPredicatePart1, overlapsPredicatePart2);
    }

    // OVERLAPS <row value predicand>
    private OverlapsPredicatePart2 tryParseOverlapsPredicatePart2() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isOverlaps()) {
            reset(markedIndex);
            return null;
        }
        RowValuePredicand rowValuePredicand = parseRowValuePredicand();
        return new OverlapsPredicatePart2(
                token.getBeginIndex(),
                rowValuePredicand
        );
    }

    // <row value predicand> <match predicate part 2>
    private MatchPredicate tryParseMatchPredicate(RowValuePredicand rowValuePredicand) {
        MatchPredicatePart2 matchPredicatePart2 = tryParseMatchPredicatePart2();
        if (matchPredicatePart2 == null) {
            return null;
        }
        return new MatchPredicate(rowValuePredicand, matchPredicatePart2);
    }

    // MATCH [ UNIQUE ] [ SIMPLE | PARTIAL | FULL ] <table subquery>
    private MatchPredicatePart2 tryParseMatchPredicatePart2() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isMatch()) {
            reset(markedIndex);
            return null;
        }
        boolean unique = false;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isUnique()) {
            unique = true;
            markedIndex1 = mark();
            token1 = scan();
        }
        boolean simple = false;
        boolean partial = false;
        boolean full = false;
        if (token1.isRegularIdentifier()) {
            if ("SIMPLE".equalsIgnoreCase(token1.getContent())) {
                simple = true;
            } else if ("PARTIAL".equalsIgnoreCase(token1.getContent())) {
                partial = true;
            } else {
                reset(markedIndex1);
            }
        } else if (token1.isFull()) {
            full = true;
        } else {
            reset(markedIndex1);
        }
        Subquery tableSubquery = parseSubquery();
        return new MatchPredicatePart2(
                token.getBeginIndex(),
                unique,
                simple,
                partial,
                full,
                tableSubquery
        );
    }

    // <string value expression> IS [ NOT ] NORMALIZED
    private NormalizedPredicate tryParseNormalizedPredicate() {
        int markedIndex = mark();
        StringValueExpression stringValueExpression = tryParseStringValueExpression();
        if (stringValueExpression == null) {
            return null;
        }
        Token token = scan();
        if (!token.isIs()) {
            reset(markedIndex);
            return null;
        }
        boolean not = false;
        Token token1 = scan();
        if (token1.isNot()) {
            not = true;
            token1 = scan();
        }
        if (!token1.isRegularIdentifier() || !"NORMALIZED".equalsIgnoreCase(token1.getContent())) {
            reset(markedIndex);
            return null;
        }
        return new NormalizedPredicate(
                token1.getEndIndex(),
                stringValueExpression,
                not
        );
    }

    // UNIQUE <table subquery>
    private UniquePredicate tryParseUniquePredicate() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isUnique()) {
            reset(markedIndex);
            return null;
        }
        Subquery tableSubquery = parseSubquery();
        return new UniquePredicate(token.getBeginIndex(), tableSubquery);
    }

    // EXISTS <table subquery>
    private ExistsPredicate tryParseExistsPredicate() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isExists()) {
            reset(markedIndex);
            return null;
        }
        Subquery tableSubquery = parseSubquery();
        return new ExistsPredicate(token.getBeginIndex(), tableSubquery);
    }

    // <row value predicand> <quantified comparison predicate part 2>
    private QuantifiedComparisonPredicate tryParseQuantifiedComparisonPredicate(RowValuePredicand rowValuePredicand) {
        QuantifiedComparisonPredicatePart2 quantifiedComparisonPredicatePart2
                = tryParseQuantifiedComparisonPredicatePart2();
        if (quantifiedComparisonPredicatePart2 == null) {
            return null;
        }
        return new QuantifiedComparisonPredicate(
                rowValuePredicand,
                quantifiedComparisonPredicatePart2
        );
    }

    // <comp op> <quantifier> <table subquery>
    private QuantifiedComparisonPredicatePart2 tryParseQuantifiedComparisonPredicatePart2() {
        CompOp compOp = tryParseCompOp();
        if (compOp == null) {
            return null;
        }
        Quantifier quantifier = parseQuantifier();
        Subquery tableSubquery = parseSubquery();
        return new QuantifiedComparisonPredicatePart2(
                compOp,
                quantifier,
                tableSubquery
        );
    }

    private Quantifier parseQuantifier() {
        Quantifier quantifier = tryParseQuantifier();
        if (quantifier != null) {
            return null;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to quantifier.");
    }

    // ALL | SOME | ANY
    private Quantifier tryParseQuantifier() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isAll()) {
            return new Quantifier(token.getBeginIndex(), token.getEndIndex(), true, false, false);
        }
        if (token.isSome()) {
            return new Quantifier(token.getBeginIndex(), token.getEndIndex(), false, true, false);
        }
        if (token.isAny()) {
            return new Quantifier(token.getBeginIndex(), token.getEndIndex(), false, false, true);
        }
        reset(markedIndex);
        return null;
    }

    private NullPredicate tryParseNullPredicate(RowValuePredicand rowValuePredicand) {
        NullPredicatePart2 nullPredicatePart2 = tryParseNullPredicatePart2();
        if (nullPredicatePart2 == null) {
            return null;
        }
        return new NullPredicate(rowValuePredicand, nullPredicatePart2);
    }

    private NullPredicatePart2 parseNullPredicatePart2() {
        NullPredicatePart2 nullPredicatePart2 = tryParseNullPredicatePart2();
        if (nullPredicatePart2 != null) {
            return nullPredicatePart2;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to null predicate part 2.");
    }

    private NullPredicatePart2 tryParseNullPredicatePart2() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isIs()) {
            reset(markedIndex);
            return null;
        }
        boolean not = false;
        Token token1 = scan();
        if (token1.isNot()) {
            not = true;
            token1 = scan();
        }
        if (!token1.isNull()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a NULL.");
        }
        return new NullPredicatePart2(token.getBeginIndex(), token1.getEndIndex(), not);
    }

    // <row value predicand> <similar predicate part 2>
    private SimilarPredicate tryParseSimilarPredicate(RowValuePredicand rowValuePredicand) {
        SimilarPredicatePart2 similarPredicatePart2 = tryParseSimilarPredicatePart2();
        if (similarPredicatePart2 == null) {
            return null;
        }
        return new SimilarPredicate(
                rowValuePredicand,
                similarPredicatePart2
        );
    }

    private SimilarPredicatePart2 parseSimilarPredicatePart2() {
        SimilarPredicatePart2 similarPredicatePart2 = tryParseSimilarPredicatePart2();
        if (similarPredicatePart2 != null) {
            return similarPredicatePart2;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to similar predicate part 2.");
    }

    // [ NOT ] SIMILAR TO <similar pattern> [ ESCAPE <escape character> ]
    private SimilarPredicatePart2 tryParseSimilarPredicatePart2() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isNot() && !token.isSimilar()) {
            reset(markedIndex);
            return null;
        }
        boolean not = false;
        if (token.isNot()) {
            not = true;
            token = scan();
        }
        if (!token.isSimilar()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isTo()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a TO.");
        }
        StringValueExpression similarPattern = parseStringValueExpression();
        EscapeCharacter escapeCharacter = null;
        int markedIndex2 = mark();
        Token token2 = scan();
        if (token2.isEscape()) {
            escapeCharacter = parseEscapeCharacter();
        } else {
            reset(markedIndex2);
        }
        return new SimilarPredicatePart2(
                token.getBeginIndex(),
                not,
                similarPattern,
                escapeCharacter
        );
    }

    // <character like predicate> | <octet like predicate>
    private LikePredicate tryParseLikePredicate(RowValuePredicand rowValuePredicand) {
        LikePredicatePart2 likePredicatePart2 = tryParseLikePredicatePart2();
        if (likePredicatePart2 == null) {
            return null;
        }
        return new LikePredicate(rowValuePredicand, likePredicatePart2);
    }

    private LikePredicatePart2 parseLikePredicatePart2() {
        LikePredicatePart2 likePredicatePart2 = tryParseLikePredicatePart2();
        if (likePredicatePart2 != null) {
            return likePredicatePart2;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Canot resolve to like predicat part 2.");
    }

    private LikePredicatePart2 tryParseLikePredicatePart2() {
        boolean not = false;
        int markedIndex = mark();
        Token token = scan();
        int beginIndex = token.getBeginIndex();
        if (token.isNot()) {
            not = true;
            token = scan();
        }
        if (!token.isLike()) {
            reset(markedIndex);
            return null;
        }
        StringValueExpression pattern = parseStringValueExpression();
        StringValueExpression escape = null;
        int markedIndex1 = mark();
        Token token1 = scan();
        if (token1.isEscape()) {
            escape = tryParseStringValueExpression();
        } else {
            reset(markedIndex1);
        }
        return new LikePredicatePart2(beginIndex, not, pattern, escape);
    }

    // <row value predicand> <in predicate part 2>
    private InPredicate tryParseInPredicate(RowValuePredicand rowValuePredicand) {
        InPredicatePart2 inPredicatePart2 = tryParseInPredicatePart2();
        if (inPredicatePart2 == null) {
            return null;
        }
        return new InPredicate(rowValuePredicand, inPredicatePart2);
    }

    private InPredicatePart2 parseInPredicatePart2() {
        InPredicatePart2 inPredicatePart2 = tryParseInPredicatePart2();
        if (inPredicatePart2 != null) {
            return inPredicatePart2;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to in predicate part 2.");
    }

    // [ NOT ] IN <in predicate value>
    private InPredicatePart2 tryParseInPredicatePart2() {
        boolean not = false;
        int markedIndex = mark();
        Token token = scan();
        if (!token.isNot()) {
            not = true;
            token = scan();
        }
        if (!token.isIn()) {
            if (not) {
                throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a IN.");
            }
            reset(markedIndex);
            return null;
        }
        InPredicateValue inPredicateValue = parseInPredicateValue();
        return new InPredicatePart2(token.getBeginIndex(), not, inPredicateValue);
    }

    private InPredicateValue parseInPredicateValue() {
        Subquery tableSubquery = tryParseSubquery();
        if (tableSubquery != null) {
            return new InPredicateValue(tableSubquery);
        }
        Token token = scan();
        if (!token.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to in predicate value.");
        }
        RowValueExpression rowValueExpression = parseRowValueExpression();
        List<RowValueExpression> inValueList = new ArrayList<>();
        inValueList.add(rowValueExpression);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            RowValueExpression rowValueExpression1 = parseRowValueExpression();
            inValueList.add(rowValueExpression1);
        }
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new InPredicateValue(token.getBeginIndex(), token1.getEndIndex(), inValueList);
    }

    // <row value predicand> <between predicate part 2>
    private BetweenPredicate tryParseBetweenPredicate() {
        int markedIndex = mark();
        RowValuePredicand rowValuePredicand = tryParseRowValuePredicand();
        if (rowValuePredicand == null) {
            return null;
        }
        BetweenPredicatePart2 betweenPredicatePart2 = tryParseBetweenPredicatePart2();
        if (betweenPredicatePart2 == null) {
            reset(markedIndex);
            return null;
        }
        return new BetweenPredicate(rowValuePredicand, betweenPredicatePart2);
    }

    private BetweenPredicatePart2 parseBetweenPredicatePart2() {
        BetweenPredicatePart2 betweenPredicatePart2 = tryParseBetweenPredicatePart2();
        if (betweenPredicatePart2 != null) {
            return betweenPredicatePart2;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve too between predicate part 2.");
    }

    // [ NOT ] BETWEEN [ ASYMMETRIC | SYMMETRIC ] <row value predicand> AND <row value predicand>
    private BetweenPredicatePart2 tryParseBetweenPredicatePart2() {
        boolean not = false;
        boolean asymmetric = false;
        boolean symmetric = false;
        int markedIndex = mark();
        Token token = scan();
        if (token.isNot()) {
            not = true;
            token = scan();
        }
        Token token1 = scan();
        if (!token1.isBetween()) {
            if (not == true) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a BETWEEN.");
            } else {
                reset(markedIndex);
                return null;
            }
        }
        int markedIndex2 = mark();
        Token token2 = scan();
        if (token2.isAsymmetric()) {
            asymmetric = true;
        } else if (token2.isSymmetric()) {
            symmetric = true;
        } else {
            reset(markedIndex2);
        }
        RowValuePredicand rowValuePredicand1 = parseRowValuePredicand();
        Token token3 = scan();
        if (!token3.isAnd()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a AND.");
        }
        RowValuePredicand rowValuePredicand2 = parseRowValuePredicand();
        return new BetweenPredicatePart2(token.getBeginIndex(), not, asymmetric, symmetric, rowValuePredicand1, rowValuePredicand2);
    }

    // <row value predicand> <comparison predicate part 2>
    private ComparisonPredicate tryParseComparisonPredicate(RowValuePredicand rowValuePredicand) {
        ComparisonPredicatePart2 comparisonPredicatePart2 = parseComparisonPredicatePart2();
        if (comparisonPredicatePart2 == null) {
            return null;
        }
        return new ComparisonPredicate(rowValuePredicand, comparisonPredicatePart2);
    }

    // <comp op> <row value predicand>
    private ComparisonPredicatePart2 parseComparisonPredicatePart2() {
        ComparisonPredicatePart2 comparisonPredicatePart2
                = tryParseComparisonPredicatePart2();
        if (comparisonPredicatePart2 != null) {
            return comparisonPredicatePart2;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to comparison predicate part 2.");
    }

    // <comp op> <row value predicand>
    private ComparisonPredicatePart2 tryParseComparisonPredicatePart2() {
        CompOp compOp = tryParseCompOp();
        if (compOp == null) {
            return null;
        }
        RowValuePredicand rowValuePredicand = parseRowValuePredicand();
        return new ComparisonPredicatePart2(compOp, rowValuePredicand);
    }

    private RowValuePredicand parseRowValuePredicand() {
        RowValuePredicand rowValuePredicand = tryParseRowValuePredicand();
        if (rowValuePredicand != null) {
            return rowValuePredicand;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to row value predicand.");
    }

    // <row value special case> | <row value constructor predicand>
    private RowValuePredicand tryParseRowValuePredicand() {
        NonparenthesizedValueExpressionPrimary rowValueSpecialCase
            = tryParseNonparenthesizedValueExpressionPrimary();
        if (rowValueSpecialCase != null) {
            return rowValueSpecialCase;
        }
        return tryParseRowValueConstructorPredicand();
    }

    private RowValueConstructorPredicand tryParseRowValueConstructorPredicand() {
        CommonValueExpression commonValueExpression = tryParseCommonValueExpression();
        if (commonValueExpression != null) {
            return commonValueExpression;
        }
        BooleanPredicand booleanPredicand = tryParseBooleanPredicand();
        if (booleanPredicand != null) {
            return booleanPredicand;
        }
        return tryParseExplicitRowValueConstructor();
    }

    private TableExpression parseTableExpression() {
        TableExpression tableExpression = tryParseTableExpression();
        if (tableExpression != null) {
            return tableExpression;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to table expression.");
    }

    // <from clause>
    // [ <where clause> ]
    // [ <group by clause> ]
    // [ <having clause> ]
    // [ <window clause> ]
    private TableExpression tryParseTableExpression() {
        FromClause fromClause = parseFromClause();
        WhereClause whereClause = tryParseWhereClause();
        GroupByClause groupByClause = tryParseGroupByClause();
        HavingClause havingClause = tryParseHavingClause();
        WindowClause windowClause = tryParseWindowClause();
        return new TableExpression(
            fromClause,
            whereClause,
            groupByClause,
            havingClause,
            windowClause
        );
    }

    private WhereClause parseWhereClause() {
        WhereClause whereClause = tryParseWhereClause();
        if (whereClause != null) {
            return whereClause;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to where clause.");
    }

    private WhereClause tryParseWhereClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isWhere()) {
            reset(markedIndex);
            return null;
        }
        BooleanValueExpression searchCondition = parseBooleanValueExpression();
        return new WhereClause(token.getBeginIndex(), searchCondition);
    }

    private GroupByClause parseGroupByClause() {
        GroupByClause groupByClause = tryParseGroupByClause();
        if (groupByClause != null) {
            return groupByClause;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to group by clause.");
    }

    private GroupByClause tryParseGroupByClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isGroup()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isBy()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a BY.");
        }
        SetQuantifier setQuantifier = tryParseSetQuantifier();
        GroupingElementList groupingElementList = parseGroupingElementList();
        return new GroupByClause(token.getBeginIndex(), setQuantifier, groupingElementList);
    }

    private GroupingElementList parseGroupingElementList() {
        GroupingElement groupingElement = parseGroupingElement();
        List<GroupingElement> groupingElementList = new ArrayList<>();
        groupingElementList.add(groupingElement);
        while (true) {
            int markedIndex2 = mark();
            Token token2 = scan();
            if (!token2.isComma()) {
                reset(markedIndex2);
                break;
            }
            GroupingElement groupingElement1 = parseGroupingElement();
            groupingElementList.add(groupingElement1);
        }
        return new GroupingElementList(groupingElementList);
    }

    private GroupingElement parseGroupingElement() {
        GroupingElement groupingElement = tryParseGroupingElement();
        if (groupingElement != null) {
            return groupingElement;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to grouping element.");
    }

    //   <ordinary grouping set>
    // | <rollup list>
    // | <cube list>
    // | <grouping sets specification>
    // | <empty grouping set>
    private GroupingElement tryParseGroupingElement() {
        OrdinaryGroupingSet ordinaryGroupingSet = tryParseOrdinaryGroupingSet();
        if (ordinaryGroupingSet != null) {
            return ordinaryGroupingSet;
        }
        RollupList rollupList = tryParseRollupList();
        if (rollupList != null) {
            return rollupList;
        }
        CubeList cubeList = tryParseCubeList();
        if (cubeList != null) {
            return cubeList;
        }
        GroupingSetsSpecification groupingSetsSpecification
                = tryParseGroupingSetsSpecification();
        if (groupingSetsSpecification != null) {
            return groupingSetsSpecification;
        }
        EmptyGroupingSet emptyGroupingSet = tryParseEmptyGroupingSet();
        if (emptyGroupingSet != null) {
            return emptyGroupingSet;
        }
        return null;
    }

    private OrdinaryGroupingSet parseOrdinaryGroupingSet() {
        OrdinaryGroupingSet ordinaryGroupingSet = tryParseOrdinaryGroupingSet();
        if (ordinaryGroupingSet != null) {
            return ordinaryGroupingSet;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to ordinary grouping set.");
    }

    //   <grouping column reference>
    // | <left paren> <grouping column reference list> <right paren>
    private OrdinaryGroupingSet tryParseOrdinaryGroupingSet() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isLeftParen()) {
            reset(markedIndex);
            ParenthesizedGroupingColumnReferenceList parenthesizedGroupingColumnReferenceList
                    = tryParseParenthesizedGroupingColumnReferenceList();
            if (parenthesizedGroupingColumnReferenceList != null) {
                return parenthesizedGroupingColumnReferenceList;
            }
        } else {
            reset(markedIndex);
            GroupingColumnReference groupingColumnReference
                    = tryParseGroupingColumnReference();
            if (groupingColumnReference != null) {
                return groupingColumnReference;
            }
        }
        return null;
    }

    private GroupingColumnReference parseGroupingColumnReference() {
        GroupingColumnReference groupingColumnReference = tryParseGroupingColumnReference();
        if (groupingColumnReference != null) {
            return groupingColumnReference;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to grouping column reference.");
    }

    // <column reference> [ <collate clause> ]
    private GroupingColumnReference tryParseGroupingColumnReference() {
        ColumnReference columnReference = tryParseColumnReference();
        if (columnReference == null) {
            return null;
        }
        CollateClause collateClause = tryParseCollateClause();
        return new GroupingColumnReference(columnReference, collateClause);
    }

    // <left paren> <grouping column reference list> <right paren>
    private ParenthesizedGroupingColumnReferenceList tryParseParenthesizedGroupingColumnReferenceList() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        GroupingColumnReference groupingColumnReference
                = parseGroupingColumnReference();
        List<GroupingColumnReference> groupingColumnReferenceList
                = new ArrayList<>();
        groupingColumnReferenceList.add(groupingColumnReference);
        while (true) {
            int markedIndex1 = mark();
            Token token1 = scan();
            if (!token1.isComma()) {
                reset(markedIndex1);
                break;
            }
            GroupingColumnReference groupingColumnReference1
                    = parseGroupingColumnReference();
            groupingColumnReferenceList.add(groupingColumnReference1);
        }
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new ParenthesizedGroupingColumnReferenceList(
                token.getBeginIndex(),
                token1.getEndIndex(),
                groupingColumnReferenceList
        );
    }

    // ROLLUP <left paren> <ordinary grouping set list> <right paren>
    private RollupList tryParseRollupList() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isRollup()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        OrdinaryGroupingSetList ordinaryGroupingSetList = parseOrdinaryGroupingSetList();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new RollupList(
                token.getBeginIndex(),
                token2.getEndIndex(),
                ordinaryGroupingSetList
        );
    }

    // CUBE <left paren> <ordinary grouping set list> <right paren>
    private CubeList tryParseCubeList() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isCube()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
        }
        OrdinaryGroupingSetList ordinaryGroupingSetList = parseOrdinaryGroupingSetList();
        Token token2 = scan();
        if (!token2.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
        }
        return new CubeList(
                token.getBeginIndex(),
                token2.getEndIndex(),
                ordinaryGroupingSetList
        );
    }

    // <ordinary grouping set> [ { <comma> <ordinary grouping set> }... ]
    private OrdinaryGroupingSetList parseOrdinaryGroupingSetList() {
        OrdinaryGroupingSet ordinaryGroupingSet = parseOrdinaryGroupingSet();
        List<OrdinaryGroupingSet> ordinaryGroupingSetList = new ArrayList<>();
        ordinaryGroupingSetList.add(ordinaryGroupingSet);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isComma()) {
                reset(markedIndex);
                break;
            }
            OrdinaryGroupingSet ordinaryGroupingSet1 = parseOrdinaryGroupingSet();
            ordinaryGroupingSetList.add(ordinaryGroupingSet1);
        }
        return new OrdinaryGroupingSetList(ordinaryGroupingSetList);
    }

    // GROUPING SETS <left paren> <grouping set list> <right paren>
    private GroupingSetsSpecification tryParseGroupingSetsSpecification() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isGrouping()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRegularIdentifier() || !"SETS".equalsIgnoreCase(token1.getContent())) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a SETS.");
        }
        Token token2 = scan();
        if (!token2.isLeftParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a (.");
        }
        GroupingSetList groupingSetList = parseGroupingSetList();
        Token token3 = scan();
        if (!token3.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token3.getBeginIndex(), "Missing a ).");
        }
        return new GroupingSetsSpecification(
                token.getBeginIndex(),
                token3.getEndIndex(),
                groupingSetList
        );
    }

    // <grouping set> [ { <comma> <grouping set> }... ]
    private GroupingSetList parseGroupingSetList() {
        GroupingSet groupingSet = parseGroupingSet();
        List<GroupingSet> groupingSetList = new ArrayList<>();
        groupingSetList.add(groupingSet);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isComma()) {
                reset(markedIndex);
                break;
            }
            GroupingSet groupingSet1 = parseGroupingSet();
            groupingSetList.add(groupingSet1);
        }
        return new GroupingSetList(groupingSetList);
    }

    //   <ordinary grouping set>
    // | <rollup list>
    // | <cube list>
    // | <grouping sets specification>
    // | <empty grouping set>
    private GroupingSet parseGroupingSet() {
        OrdinaryGroupingSet ordinaryGroupingSet
                = tryParseOrdinaryGroupingSet();
        if (ordinaryGroupingSet != null) {
            return ordinaryGroupingSet;
        }
        RollupList rollupList = tryParseRollupList();
        if (rollupList != null) {
            return rollupList;
        }
        CubeList cubeList = tryParseCubeList();
        if (cubeList != null) {
            return cubeList;
        }
        GroupingSetsSpecification groupingSetsSpecification
                = tryParseGroupingSetsSpecification();
        if (groupingSetsSpecification != null) {
            return groupingSetsSpecification;
        }
        EmptyGroupingSet emptyGroupingSet = tryParseEmptyGroupingSet();
        if (emptyGroupingSet != null) {
            return emptyGroupingSet;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to grouping set.");
    }

    // <left paren> <right paren>
    private EmptyGroupingSet tryParseEmptyGroupingSet() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (!token1.isRightParen()) {
            reset(markedIndex);
            return null;
        }
        return new EmptyGroupingSet(token.getBeginIndex(), token1.getEndIndex());
    }

    private HavingClause parseHavingClause() {
        HavingClause havingClause = tryParseHavingClause();
        if (havingClause != null) {
            return havingClause;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to having clause.");
    }

    private HavingClause tryParseHavingClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isHaving()) {
            reset(markedIndex);
            return null;
        }
        BooleanValueExpression searchCondition = parseBooleanValueExpression();
        return new HavingClause(token.getBeginIndex(), searchCondition);
    }

    private WindowClause parseWindowClause() {
        WindowClause windowClause = tryParseWindowClause();
        if (windowClause != null) {
            return windowClause;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to window clause.");
    }

    private WindowClause tryParseWindowClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isWindow()) {
            reset(markedIndex);
            return null;
        }
        WindowDefinitionList windowDefinitionList = parseWindowDefinitionList();
        return new WindowClause(token.getBeginIndex(), windowDefinitionList);
    }

    private WindowDefinitionList parseWindowDefinitionList() {
        WindowDefinition windowDefinition = parseWindowDefinition();
        List<WindowDefinition> windowDefinitionList = new ArrayList<>();
        windowDefinitionList.add(windowDefinition);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isComma()) {
                reset(markedIndex);
                break;
            }
            WindowDefinition windowDefinition1 = parseWindowDefinition();
            windowDefinitionList.add(windowDefinition1);
        }
        return new WindowDefinitionList(windowDefinitionList);
    }

    // <new window name> AS <window specification>
    private WindowDefinition parseWindowDefinition() {
        Identifier newWindowName = parseIdentifier();
        Token token = scan();
        if (!token.isAs()) {
            throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a AS.");
        }
        WindowSpecification windowSpecification = parseWindowSpecification();
        return new WindowDefinition(newWindowName, windowSpecification);
    }

    private CompOp parseCompOp() {
        CompOp compOp = tryParseCompOp();
        if (compOp != null) {
            return compOp;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to comp op.");
    }

    //   <equals operator>
    // | <not equals operator>
    // | <less than operator>
    // | <greater than operator>
    // | <less than or equals operator>
    // | <greater than or equals operator>
    private CompOp tryParseCompOp() {
        boolean equalsOperator = false;
        boolean notEqualsOperator = false;
        boolean lessThanOperator = false;
        boolean greaterThanOperator = false;
        boolean lessThanOrEqualsOperator = false;
        boolean greaterThanOrEqualsOperator = false;
        int markedIndex = mark();
        Token token = scan();
        if (token.isEqualsOperator()) {
            equalsOperator = true;
        } else if (token.isNotEqualsOperator()) {
            notEqualsOperator = true;
        } else if (token.isLessThanOperator()) {
            lessThanOperator = true;
        } else if (token.isGreaterThanOperator()) {
            greaterThanOperator = true;
        } else if (token.isLessThanOrEqualsOperator()) {
            lessThanOrEqualsOperator = true;
        } else if (token.isGreaterThanOrEqualsOperator()) {
            greaterThanOrEqualsOperator = true;
        } else {
            reset(markedIndex);
            return null;
        }
        return new CompOp(
            token.getBeginIndex(),
            token.getEndIndex(),
            equalsOperator,
            notEqualsOperator,
            lessThanOperator,
            greaterThanOperator,
            lessThanOrEqualsOperator,
            greaterThanOrEqualsOperator
        );
    }

    // <parenthesized boolean value expression> | <nonparenthesized value expression primary>
    private BooleanPredicand tryParseBooleanPredicand() {
        ParenthesizedBooleanValueExpression parenthesizedBooleanValueExpression
            = tryParseParenthesizedBooleanValueExpression();
        if (parenthesizedBooleanValueExpression != null) {
            return parenthesizedBooleanValueExpression;
        }
        return tryParseNonparenthesizedValueExpressionPrimary();
    }

    private ParenthesizedBooleanValueExpression tryParseParenthesizedBooleanValueExpression() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        BooleanValueExpression booleanValueExpression = parseBooleanValueExpression();
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new ParenthesizedBooleanValueExpression(token.getBeginIndex(), token1.getEndIndex(), booleanValueExpression);
    }

    // TRUE | FALSE | UNKNOWN
    private TruthValue parseTruthValue() {
        Token token = scan();
        if (token.isTrue()) {
            return new TruthValue(token.getBeginIndex(), token.getEndIndex(), true, false, false);
        }
        if (token.isFalse()) {
            return new TruthValue(token.getBeginIndex(), token.getEndIndex(), false, true, false);
        }
        if (token.isUnknown()) {
            return new TruthValue(token.getBeginIndex(), token.getEndIndex(), false, false, true);
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Missing a TRUE, FALSE or UNKNOWN.");
    }

    private RowValueExpression parseRowValueExpression() {
        RowValueExpression rowValueExpression = tryParseRowValueExpression();
        if (rowValueExpression != null) {
            return rowValueExpression;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to row value expression.");
    }

    // <row value special case> | <explicit row value constructor>
    private RowValueExpression tryParseRowValueExpression() {
        NonparenthesizedValueExpressionPrimary rowValueSpecialCase = tryParseNonparenthesizedValueExpressionPrimary();
        if (rowValueSpecialCase != null) {
            return rowValueSpecialCase;
        }
        return tryParseExplicitRowValueConstructor();
    }

    //   <left paren> <row value constructor element> <comma> <row value constructor element list> <right paren>
    // | ROW <left paren> <row value constructor element list> <right paren>
    // | <row subquery>
    private ExplicitRowValueConstructor tryParseExplicitRowValueConstructor() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isRow()) {
            Token token1 = scan();
            if (!token1.isLeftParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a (.");
            }
            List<ValueExpression> rowValueConstructorElementList = parseValueExpressionList();
            Token token2 = scan();
            if (!token2.isLeftParen()) {
                throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
            }
            return new ExplicitRowValueConstructor(
                    token.getBeginIndex(),
                    token2.getEndIndex(),
                    rowValueConstructorElementList == null ? null : new ValueExpressionList(rowValueConstructorElementList)
            );
        }
        if (token.isLeftParen()) {
            ValueExpression rowValueConstructorElement = tryParseValueExpression();
            if (rowValueConstructorElement != null) {
                Token token1 = scan();
                if (!token1.isComma()) {
                    throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ,.");
                }
                List<ValueExpression> rowValueConstructorElementList = parseValueExpressionList();
                Token token2 = scan();
                if (!token2.isLeftParen()) {
                    throw new YuliaException(getSql(), getLineNumber(), token2.getBeginIndex(), "Missing a ).");
                }
                return new ExplicitRowValueConstructor(
                        token.getBeginIndex(),
                        token2.getEndIndex(),
                        rowValueConstructorElement,
                        rowValueConstructorElementList == null ? null : new ValueExpressionList(rowValueConstructorElementList)
                );
            }
            reset(markedIndex);
            Subquery subquery = parseSubquery();
            return new ExplicitRowValueConstructor(subquery);
        }
        reset(markedIndex);
        return null;
    }

    private List<ValueExpression> parseValueExpressionList() {
        ValueExpression valueExpression = parseValueExpression();
        List<ValueExpression> valueExpressionList = new ArrayList<>();
        valueExpressionList.add(valueExpression);
        while (true) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isComma()) {
                reset(markedIndex);
                break;
            }
            ValueExpression valueExpression1 = parseValueExpression();
            valueExpressionList.add(valueExpression1);
        }
        return valueExpressionList;
    }

    private LanguageClause tryParseLanguageClause() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLanguage()) {
            reset(markedIndex);
            return null;
        }
        Token token1 = scan();
        if (token1.isRegularIdentifier()) {
            if ("ADA".equalsIgnoreCase(token1.getContent())) {
                return new LanguageClause(token.getBeginIndex(), token1.getEndIndex(), LanguageName.ADA);
            }
            if ("C".equalsIgnoreCase(token1.getContent())) {
                return new LanguageClause(token.getBeginIndex(), token1.getEndIndex(), LanguageName.C);
            }
            if ("COBOL".equalsIgnoreCase(token1.getContent())) {
                return new LanguageClause(token.getBeginIndex(), token1.getEndIndex(), LanguageName.COBOL);
            }
            if ("FORTRAN".equalsIgnoreCase(token1.getContent())) {
                return new LanguageClause(token.getBeginIndex(), token1.getEndIndex(), LanguageName.FORTRAN);
            }
            if ("MUMPS".equalsIgnoreCase(token1.getContent())) {
                return new LanguageClause(token.getBeginIndex(), token1.getEndIndex(), LanguageName.MUMPS);
            }
            if ("PASCAL".equalsIgnoreCase(token1.getContent())) {
                return new LanguageClause(token.getBeginIndex(), token1.getEndIndex(), LanguageName.PASCAL);
            }
            if ("PLI".equalsIgnoreCase(token1.getContent())) {
                return new LanguageClause(token.getBeginIndex(), token1.getEndIndex(), LanguageName.PLI);
            }
        } else if (token1.isSql()) {
            return new LanguageClause(token.getBeginIndex(), token1.getEndIndex(), LanguageName.SQL);
        }
        throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a language name.");
    }

    private RoutineInvocation parseRoutineInvocation() {
        RoutineInvocation routineInvocation = tryParseRoutineInvocation();
        if (routineInvocation != null) {
            return routineInvocation;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to routine invocation.");
    }

    // <routine name> <SQL argument list>
    private RoutineInvocation tryParseRoutineInvocation() {
        SchemaQualifiedName routineName = tryParseSchemaQualifiedName();
        if (routineName == null) {
            return null;
        }
        SqlArgumentList sqlArgumentList = parseSqlArgumentList();
        return new RoutineInvocation(routineName, sqlArgumentList);
    }

    private SqlArgumentList parseSqlArgumentList() {
        SqlArgumentList sqlArgumentList = tryParseSqlArgumentList();
        if (sqlArgumentList != null) {
            return sqlArgumentList;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to sql argument list.");
    }

    // <left paren> [ <SQL argument> [ { <comma> <SQL argument> }... ] ] <right paren>
    private SqlArgumentList tryParseSqlArgumentList() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isLeftParen()) {
            reset(markedIndex);
            return null;
        }
        List<SqlArgument> sqlArgumentList = null;
        SqlArgument sqlArgument = tryParseSqlArgument();
        if (sqlArgument != null) {
            sqlArgumentList = new ArrayList<>();
            sqlArgumentList.add(sqlArgument);
            while (true) {
                int markedIndex1 = mark();
                Token token1 = scan();
                if (!token1.isComma()) {
                    reset(markedIndex1);
                    break;
                }
                SqlArgument sqlArgument1 = parseSqlArgument();
                sqlArgumentList.add(sqlArgument1);
            }
        }
        Token token1 = scan();
        if (!token1.isRightParen()) {
            throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a ).");
        }
        return new SqlArgumentList(
                token.getBeginIndex(),
                token1.getEndIndex(),
                sqlArgumentList
        );
    }

    private SqlArgument parseSqlArgument() {
        int markedIndex = mark();
        Token token = scan();
        reset(markedIndex);
        SqlArgument sqlArgument = tryParseSqlArgument();
        if (sqlArgument != null) {
            return sqlArgument;
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to SQL argument.");
    }

    //   <value expression>
    // | <generalized expression>
    // | <target specification>
    private SqlArgument tryParseSqlArgument() {
        ValueExpression valueExpression = tryParseValueExpression();
        if (valueExpression != null) {
            int markedIndex = mark();
            Token token = scan();
            if (!token.isAs()) {
                reset(markedIndex);
                return valueExpression;
            }
            SchemaQualifiedName pathResolvedUserDefinedTypeName
                    = parseSchemaQualifiedName();
            return new GeneralizedExpression(
                    valueExpression,
                    pathResolvedUserDefinedTypeName
            );
        }
        TargetSpecification targetSpecification = tryParseTargetSpecification();
        if (targetSpecification != null) {
            return targetSpecification;
        }
        return null;
    }

    private EmbeddedExceptionDeclaration parseEmbeddedExceptionDeclaration() {
        EmbeddedExceptionDeclaration embeddedExceptionDeclaration = tryParseEmbeddedExceptionDeclaration();
        if (embeddedExceptionDeclaration != null) {
            return embeddedExceptionDeclaration;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to embedded exception declaration.");
    }

    // WHENEVER <condition> <condition action>
    private EmbeddedExceptionDeclaration tryParseEmbeddedExceptionDeclaration() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isWhenever()) {
            reset(markedIndex);
            return null;
        }
        SqlCondition condition = parseSqlCondition();
        ConditionAction conditionAction = parseConditionAction();
        return new EmbeddedExceptionDeclaration(
                token.getBeginIndex(),
                condition,
                conditionAction
        );
    }

    //   <major category>
    // | SQLSTATE ( <SQLSTATE class value> [ , <SQLSTATE subclass value> ] )
    // | CONSTRAINT <constraint name>
    private SqlCondition parseSqlCondition() {
        MajorCategory majorCategory = tryParseMajorCategory();
        if (majorCategory != null) {
            return majorCategory;
        }
        SqlstateSqlCondition sqlstateSqlCondition = tryParseSqlstateSqlCondition();
        if (sqlstateSqlCondition != null) {
            return sqlstateSqlCondition;
        }
        ConstraintSqlCondition constraintSqlCondition = tryParseConstraintSqlCondition();
        if (constraintSqlCondition != null) {
            return constraintSqlCondition;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to SQL condition.");
    }

    // SQLEXCEPTION | SQLWARNING | NOT FOUND
    private MajorCategory tryParseMajorCategory() {
        int markedIndex = mark();
        Token token = scan();
        if (token.isSqlexception()) {
            return new MajorCategory(token.getBeginIndex(), token.getEndIndex(), true, false, false);
        }
        if (token.isSqlwarning()) {
            return new MajorCategory(token.getBeginIndex(), token.getEndIndex(), false, true, false);
        }
        if (token.isNot()) {
            Token token1 = scan();
            if (!token1.isRegularIdentifier() || !"FOUND".equalsIgnoreCase(token1.getContent())) {
                throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a FOUND.");
            }
            return new MajorCategory(token.getBeginIndex(), token1.getEndIndex(), false, false, true);
        }
        reset(markedIndex);
        return null;
    }

    // SQLSTATE ( <SQLSTATE class value> [ , <SQLSTATE subclass value> ] )
    private SqlstateSqlCondition tryParseSqlstateSqlCondition() {
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "The SQL condition is not supported.");
    }

    // CONSTRAINT <constraint name>
    private ConstraintSqlCondition tryParseConstraintSqlCondition() {
        int markedIndex = mark();
        Token token = scan();
        if (!token.isConstraint()) {
            reset(markedIndex);
            return null;
        }
        SchemaQualifiedName constraintName = parseSchemaQualifiedName();
        return new ConstraintSqlCondition(token.getBeginIndex(), constraintName);
    }

    // CONTINUE | { GOTO | GO TO } <goto target>
    private ConditionAction parseConditionAction() {
        Token token = scan();
        if (token.isContinue()) {
            return new ConditionAction(token.getBeginIndex(), token.getEndIndex());
        }
        if (token.isRegularIdentifier()) {
            if ("GOTO".equalsIgnoreCase(token.getContent()) || "GO".equalsIgnoreCase(token.getContent())) {
                if ("GO".equalsIgnoreCase(token.getContent())) {
                    Token token1 = scan();
                    if (!token1.isTo()) {
                        throw new YuliaException(getSql(), getLineNumber(), token1.getBeginIndex(), "Missing a TO.");
                    }
                }
                GotoTarget gotoTarget = parseGotoTarget();
                return new ConditionAction(token.getBeginIndex(), gotoTarget);
            }
        }
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "Cannot resolve to condition action.");
    }

    //   <host label identifier>
    // | <unsigned integer>
    // | <host PL/I label variable>
    private GotoTarget parseGotoTarget() {
        UnsignedInteger unsignedInteger = tryParseUnsignedInteger();
        if (unsignedInteger != null) {
            return unsignedInteger;
        }
        Token token = scan();
        throw new YuliaException(getSql(), getLineNumber(), token.getBeginIndex(), "This goto target type is not supported.");
    }

    private Token scan() {
        if (bufferBeginIndex < buffer.size()) {
            return buffer.get(bufferBeginIndex++);
        }
        Token token = scanner.scan();
        if (!token.isEof()) {
            if (buffer.isEmpty()) {
                buffer.add(token);
            } else {
                Token lastTokenOfBuffer = buffer.get(buffer.size() - 1);
                if (!lastTokenOfBuffer.isEof()) {
                    buffer.add(token);
                }
            }
        } else {
            buffer.add(token);
        }
        return token;
    }

    private int mark() {
        return bufferBeginIndex;
    }

    private void reset(int markedIndex) {
        bufferBeginIndex = markedIndex;
    }

    void clean() {
        buffer.clear();
        bufferBeginIndex = 0;
        scanner.clean();
    }

    public void close() {
        buffer.clear();
        bufferBeginIndex = 0;
        scanner.close();
    }

    public String getSql() {
        return scanner.getSql();
    }

    public int getLineNumber() {
        return scanner.getLineNumber();
    }

}
